検索

C/C++の詳細な分析

Nov 22, 2016 pm 05:21 PM
c/c++

配列

配列は開発において一般的なデータ構造です。もちろん、配列がコンテナに相当することはわかっていますが、値や文字を保存するだけでなく、関数のエントリアドレスも保存できます。そして構造データ。

typedef  struct _value
{
       int val_1;
       int val_2;
}VALUE;
typedef  struct _table
{
       char  index;
       char  data[100];
int (*UniSetFunc)(VALUE*);
}TABLE;
 
int  add(VALUE  *val )
{
       int  temp = 0;
       temp = val->val_1 + val->val_2;
       return  temp;
}
 
TABLE  page[10]
{
       {“ First section ”, “abcdefghijklmn”, add},
       {“Second section”, “opqrstuvwxyz”, NULL}
};
int main()
{
       VALUE  AddValue;
       AddValue.val_1 = 2;
       AddValue.val_2 = 4;
       int  result = 0;
       result  = page[0]-> UniSetFunc(&AddValue);
       printf(“The Result of add is %d\n”, result);
       return 0;
}

このとき、配列は Python 言語の辞書に似た構造に変換され、その後の開発や利用、追加のアップグレードやメンテナンスが容易になります。

コード分析: まず、関数の名前は関数のエントリ アドレスとして使用できることがわかっています (配列のアドレスを表す配列の名前と同様)。そのため、TABLE 構造で次のように定義します。メンバー関数 int (*UniSetFunc) (VALUE *); ここで、UniSetFunc は関数のエントリ パラメーターとして機能し、VALUE* は関数の仮パラメーターの型を表し、TABLE 型配列 page[10] には構造体のデータが含まれます。関数のエントリ アドレス。page[0]-> UniSetFunc(&AddValue) を呼び出して、間接的に加算関数を呼び出し、AddValue 内の 2 つの数値 AddValue.val_1 と AddValue.val_1 の合計演算を実装します。

メモリヒット率の問題:

コード動作の効率を向上させるためには、メモリ空間を最大限に活用し、メモリ領域に継続的にアクセスする必要があります。

配列がメモリに格納される場所は全領域であり、定義された配列array[2][2]について、array[0][0]のアドレスが0x04030であると仮定します。の場合、array[0][1]、array[1][0]、および array[1][1] のアドレスはそれぞれ 0x04031、0x04032、0x04033 となり、メモリのヒット率を向上させるには、次のようにメモリにアクセスする必要があります。ジャンプアクセスではなく、可能な限り連続的にスペース領域にアクセスします。 次に、行列の乗算の問題を見てみましょう。

              for (int i = 0; i < 2; ++i)
              {
                            for (int j = 0; j < 2; ++j)
                            {
                                          for (int k = 0; k < 3; ++k)
                                          {
                                                        matrix[i][j] += matrix1[i][k] * matrix2[k][j];
                                          }
                            }
              }

上記のコードは、行列行列 1 と行列 2 を乗算して行列に代入する一般的な方法です。つまり、行列 1 の行列を使用して行列行列 2 の列ベクトルを乗算した行ベクトルを取得し、それを行列に代入します。このように、メモリ内の行列格納の構造上、行列 2 へのアクセスでは連続アクセス方式が使用されていないため、メモリのヒット率が低いことがわかります。次に、メモリヒット率の高い方法を見ていきます。

for (int i = 0; i < 2; ++i)
       {
                     for (int k = 0; k < 3; ++k)
                     {
                                   for (int j = 0; j < 2; ++j)
                                   {
                                                 matrix[i][j] += matrix1[i][k] * matrix2[k][j];
                                   }
                     }
       }

このコードでは、2 番目の for ループと 3 番目の for ループの位置が入れ替わっているだけで、他の部分は変わっていないことがわかります。ただし、行列 1 と行列 2 を使用することで、メモリのヒット率が大幅に向上しています。行列 内部要素は 1 つずつ乗算され、行列の乗算のために累積されます。このようにして、行列 1 および行列 2 にアクセスするときにメモリ ミスが発生しないため、メモリ ヒットの可能性が高まります。




volatile、const、static の関係:


const キーワードは定数キーワードであり、それが作用する量は定数であり、プログラムは変更できませんconst int value = 12 などの定数の値。この定数値は、プログラムが固定定数を誤って変更することを防ぐために、プログラムで頻繁に使用されます。 const キーワードを適時に追加する必要があります。また、const キーワードが定数に作用する場合は、プログラム全体で変更することができないため、すぐに初期化する必要があります。例: const int value = 12 は正しいですが、 const int value = 12; の構文は間違っています。 次に、定数ポインタとポインタ定数について勉強してみましょう。まずコードの一部を見てみましょう:

#define SWITCH 1
int main()
{
       int val_1 = 5;
       int val_2 = 10;
       const int *p1 = &val_1;
       int const *p2 = &val_1;
       int *const p3 = &val_1;
#ifdef  SWITCH          // This is a switch
       *p1 = 20;
       *p2 = 21;
       *p3 = 22;
#endif
#ifndef  SWITCH
       p1 = &val_2;
       p2 = &val_2;
       p3 = &val_2;
#endif
       printf("%d\n", *p1);
       printf("%d\n", *p2);
       printf("%d\n", *p3);
       return 0;
}


cygwin コンパイラで実行すると、次のエラーが表示されます:



この図から、ポインター p1 と p2 がはっきりとわかります。 val_1 の値はポインタ定数としてのみ読み取ることができます。つまり、それが指す変数の内容を変更することはできないため、*p1 = 20; *p2 = 21; という 2 つのコマンドは間違っています。 (#ifdef SWITCH ... #endif は条件付きコンパイル、つまりマクロ スイッチです)。次に、#define SWITCH 1 ステートメントをコメント アウトします。この時点で、コードの 2 番目のブロックを実行し、次の結果が得られます。エラーから、p3 は固定アドレスのみを指すことができ、指す方向を変更できないことがわかります。したがって、p3 = &val_2; の演算は間違っています。次のようになります:

int main()
{
              int val_1 = 5;
              int val_2 = 10;
              const int *p1 = &val_1;
              int const *p2 = &val_1;
              int *const p3 = &val_1;
              printf("Frist\n");
              printf("%d\n", *p1);
              printf("%d\n", *p2);
              printf("%d\n", *p3);
              p1 = &val_2;
              p2 = &val_2;
              *p3 = 22;
              printf("Second\n");
              printf("%d\n", *p1);
              printf("%d\n", *p2);
              printf("%d\n", *p3);
              return 0;
}


結果は次のようになります:



最終終了: 定数ポインター (const int *p または int const *p) は、ポインター p が格納されている値を変更できないことを意味します。アドレス; ポインター定数 (int *const p) は、ポインター p が指すアドレスを変更できない、つまり、ポインターが指す位置を変更できないことを意味します。ただし、それが指す場所のコンテンツを変更することはできます。ポインタが指す位置もコンテンツも変更できないようにしたい場合は、次のように定義できます:


const int * const p = &a;或int const *const p = &a; 在定义函数的时候,若该入口参数在程序执行的过程中不希望被改变,则一定要将该形参用const来修饰,一来这样可以防止该段程序将其改变,二来对于形参而言,一个无论是否是const修饰的实参都可以将其传入const形的形参,而一个const形的实参是无法传入非const形的形参中,所以为了使编译不出错在定义函数的时候,一定要将不希望被改变的量用const关键字来修饰。


Static关键字为静态关键字,它的作用是将作用的变量存入内存中,而非存入寄存器中(即将变量存入堆中而非栈中),并且该作用的变量仅保存最近一次获取的值。接下来我们来看一段代码。

void  countfun ()
{
       static  int  count = 0;
++count;
printf(“This is %d number, enter into this function !\n”, count );
}
int main()
{
       for (int i = 0; i < 5; ++i)
       {
                     countfun();
}
return 0;
}


这段代码的运行结果如下:





而若将除去static关键字,则运行的结果如下:



由此我们可以清楚的看出,static作用的变量count只会存入当前的结果,因此循环调用countfun( )函数的时候并没有从新将count变量置为0,而是保存了前一次的值。


Static关键字在项目中的应用是很广泛的,它不仅仅有上述所介绍的特点,同时若想要定义的全局变量在整个工程中仅在当前.C文件中有效时,也应该将这个全局变量用static来修饰,这样在其他的文件中是无法访问这个变量,从而降低了模块间的耦合度,提高了模块的内聚性,防止其他文件将其改变,从而更加的安全。


volatile关键字在嵌入式领域中是十分重要的一个关键字,尤其是在与硬件相关或多线程的编程中更为重要。volatile关键字修饰的变量说明它是可以随时发生改变的,我们不希望编译器去优化某些代码的时候,需要将这个变量用volatile关键字来修饰,从而程序每次访问该变量的时候是直接从内存中提取出来,而不是从临时的寄存器中将该变量的副本给提取出来利用!例如当我们想要实现某个中断处理时,其用来做判断条件的标记位则应该用volatile来修饰,这样当这个中断在别的地方被触发的时候就可以被实时的检测到,不至于由于优化而忽略中断。接下来我们看一段代码:

int main()
{
    volatile int i = 10;
    int a = i;
    printf(“i = %d\n”, a);
__asm
{
        mov dword ptr[ebp-4], 0x10
}
int b = i;
printf(“i = %d\n”, b);
return 0;
}

此程序输出结果为i = 10;i = 16; 若将volatile关键字去掉,则结果为i = 10;i = 10;




即不加关键字会将汇编代码忽略掉,所以为了防止代码优化以及可以及时检测到外部程序对该变量的改变,我们必须将该变量加上volatile关键字。我们知道volatile关键字表征该量是易变的,const关键字代表该量是常量不能改变,那么volatile与const是否可以一起修饰同一个量呢,是肯定的,例如在硬件编程中ROM所存储的数据是不允许用户改变的,即指向该数据的指针必须为常量指针(const int *p = &ram_data),然而开发商却可以将其意外的改变,为了防止ROM的内容被意外的改变时,而用户程序没有及时的发现,必须将该量用volatile修饰,所以应这样定义该指针(volatile const int *p = &rom_data)。





位运算


在数字解码与编码的过程中,位运算的操作是司空见惯的事,同时位运算在提高程序的性能方面也独占鳌头,因此位运算操作是必需要深入了解的问题。



在乘法以及除法的操作中我可以使用未运行来提高代码的质量,例如:a = a * 16;这种操作完全可以替换为:a = a


在数据类型转换的过程中也需要做位运算操作,例如我们想将一个unsigned short类型的数据存入unsigned char类型的数组中,就需要进行位运算,首先分析知道unsigned short占用16个字节,unsigned char占用8个字节,想要将大字节的数据存入小字节,必须要对大字节进行分割,即将高8位与低8为分离开来分别存放,来看实现代码:

unsigned char * DrawCompo_Pj_BT_Change(unsigned short *subarray)
{
    unsigned char temp[500];
    (void)_sys_memset(&temp, 0x00, sizeof(temp) );
    unsigned short i = 0;
    while (subarray[i] != 0x0000)
    {
            if( (subarray[i] & 0xff00)  == 0x0000)
            {
                    temp[i++] = (unsigned char)(subarray[i] & 0x00ff);
            }
            else
            {
                    temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8);
                    temp[i++] = (unsigned char)(subarray[i] & 0x00ff);
            }
    }
    temp[i] = &#39;\0&#39;;
    return temp;
}


temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8);即取subarray[i]数据的高8位,temp[i++] = (unsigned char)(subarray[i] & 0x00ff);取低8位。这样就可以实现将高字节的数据完整的存入到低字节中。


位运算还可以用来判断变量的符号,我们知道对于一个有符号的变量,其最高位为其符号位,故检查改变的最高位即可知道该变量为正还是为负。看一段代码:

int main()
{
    short test_data = -12;
    if (test_data & 0xF000)
    {
            printf("This number is negative ");
    }
    else
    {
            printf("This number is positive ");
    }
    return 0;
}


对于想要交换两个数的值的时候,通常我们的做法如下:

void swap(int &data1, int &data2)
{
    int temp = 0;
    temp = data1;
    data1 = data2;
    data2 = temp;
}


这样的代码比较简单易懂,然而美中不足的是它会产生一个临时变量temp,接下来我们用位运算来重写这个程序;

void swap(int &data1, int &data2)
{
    data1 = data1 ^ data2;
    data2 = data1 ^ data2;
    data1 = data1 ^ data2;
}

从上面的代码我们可以看出少了一个临时变量,同时也加快了代码的运行效率。


尾递归:


递归调用给我们带来了很多方便,也简化了代码,使程序看起来更加的简洁和明了,但递归调用也通常伴随着一个潜在的危险:出栈,接下来我们来看一个常见的递归。


int factorial(int n)
{
    if (n < 1)
    {
            return 1;
    }
    else
    {
            return factorial(n-1)*n;
    }
   
}

通常在求一个数的阶乘的时候我们习惯于采用上述方法,然而分析来看当输入的n值较大时,factorial(n-1)*n所计算的值会不断的压入堆栈,生成很多的临时变量,等待下一个的值的确定才得以计算,然而在内存中堆栈的大小是固定的,当输入的n值很大时,极有可能产生堆栈溢出!因此,有一个好的方法解决这种问题即尾递归调用。接下来我们来看这种强大的算法。

int factorial(int n, int m)
{
    if (n < 2)
    {
            return m;
    }
    else
    {
            factorial(n-1, n*m);
    }
}

从代码中可以看出,通过引入一个新的参数m来存放每次递归所产生的值,这样就避免了每次递归都要进行压栈操作,也就不会产生堆栈溢出的现象,而且普通的递归每次递归只能等待下一次递归得到的结果后才能继续运算,而尾递归每次执行都会进行运算,一次循环执行完毕即可得到结果,其时间复杂度为O(n);


声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
c#.netはあなたにぴったりですか?その適用性の評価c#.netはあなたにぴったりですか?その適用性の評価Apr 13, 2025 am 12:03 AM

c#.netissuitableforenterprise-levelApplicationsとsystemduetoitsSystemdutyping、richlibraries、androbustperformance.

.NET内のC#コード:プログラミングプロセスの調査.NET内のC#コード:プログラミングプロセスの調査Apr 12, 2025 am 12:02 AM

.NETでのC#のプログラミングプロセスには、次の手順が含まれます。1)C#コードの作成、2)中間言語(IL)にコンパイルし、3).NETランタイム(CLR)によって実行される。 .NETのC#の利点は、デスクトップアプリケーションからWebサービスまでのさまざまな開発シナリオに適した、最新の構文、強力なタイプシステム、および.NETフレームワークとの緊密な統合です。

C#.NET:コアの概念とプログラミングの基礎を探るC#.NET:コアの概念とプログラミングの基礎を探るApr 10, 2025 am 09:32 AM

C#は、Microsoftによって開発された最新のオブジェクト指向プログラミング言語であり、.NETフレームワークの一部として開発されています。 1.C#は、カプセル化、継承、多型を含むオブジェクト指向プログラミング(OOP)をサポートしています。 2。C#の非同期プログラミングは非同期を通じて実装され、適用応答性を向上させるためにキーワードを待ちます。 3. LINQを使用してデータ収集を簡潔に処理します。 4.一般的なエラーには、null参照の例外と、範囲外の例外インデックスが含まれます。デバッグスキルには、デバッガーと例外処理の使用が含まれます。 5.パフォーマンスの最適化には、StringBuilderの使用と、不必要な梱包とボクシングの回避が含まれます。

テストC#.NETアプリケーション:ユニット、統合、およびエンドツーエンドテストテストC#.NETアプリケーション:ユニット、統合、およびエンドツーエンドテストApr 09, 2025 am 12:04 AM

C#.NETアプリケーションのテスト戦略には、ユニットテスト、統合テスト、エンドツーエンドテストが含まれます。 1.単位テストにより、コードの最小ユニットがMSTEST、ヌニット、またはXUNITフレームワークを使用して独立して動作することを保証します。 2。統合テストでは、一般的に使用されるシミュレートされたデータと外部サービスを組み合わせた複数のユニットの機能を検証します。 3.エンドツーエンドのテストでは、ユーザーの完全な操作プロセスをシミュレートし、通常、セレンは自動テストに使用されます。

高度なC#.NETチュートリアル:次のシニア開発者インタビューをエース高度なC#.NETチュートリアル:次のシニア開発者インタビューをエースApr 08, 2025 am 12:06 AM

C#シニア開発者とのインタビューでは、非同期プログラミング、LINQ、.NETフレームワークの内部作業原則などのコア知識をマスターする必要があります。 1.非同期プログラミングは、非同期を通じて操作を簡素化し、アプリケーションの応答性を向上させるのを待ちます。 2.LinqはSQLスタイルでデータを操作し、パフォーマンスに注意を払います。 3.ネットフレームワークのCLRはメモリを管理し、ガベージコレクションに注意して使用する必要があります。

c#.netインタビューの質問と回答:専門知識を高めるc#.netインタビューの質問と回答:専門知識を高めるApr 07, 2025 am 12:01 AM

C#.NETインタビューの質問と回答には、基本的な知識、コアの概念、高度な使用が含まれます。 1)基本知識:C#は、Microsoftが開発したオブジェクト指向言語であり、主に.NETフレームワークで使用されています。 2)コアの概念:委任とイベントは動的な結合方法を可能にし、LINQは強力なクエリ関数を提供します。 3)高度な使用:非同期プログラミングは応答性を向上させ、式ツリーは動的コード構造に使用されます。

C#.NETを使用したマイクロサービスの構築:建築家向けの実用的なガイドC#.NETを使用したマイクロサービスの構築:建築家向けの実用的なガイドApr 06, 2025 am 12:08 AM

C#.NETは、その強力なエコシステムと豊富なサポートのため、マイクロサービスを構築するために人気のある選択肢です。 1)asp.netcoreを使用してRestfulapiを作成して、順序の作成とクエリを処理します。 2)GRPCを使用して、マイクロサービス間の効率的な通信を実現し、注文サービスを定義および実装します。 3)Dockerコンテナ化されたマイクロサービスを介して展開と管理を簡素化します。

C#.NETセキュリティベストプラクティス:一般的な脆弱性の防止C#.NETセキュリティベストプラクティス:一般的な脆弱性の防止Apr 05, 2025 am 12:01 AM

C#および.NETのセキュリティベストプラクティスには、入力検証、出力エンコード、例外処理、認証と承認が含まれます。 1)正規表現または組み込みのメソッドを使用して入力を検証して、悪意のあるデータがシステムに入るのを防ぎます。 2)XSS攻撃を防ぐための出力エンコード、httputility.htmlencodeメソッドを使用します。 3)例外処理により、情報の漏れが回避され、エラーが記録されますが、詳細情報はユーザーに返されません。 4)ASP.Netidentityおよび請求に基づく許可を使用して、不正アクセスから申請を保護します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

Dreamweaver Mac版

Dreamweaver Mac版

ビジュアル Web 開発ツール

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

VSCode Windows 64 ビットのダウンロード

VSCode Windows 64 ビットのダウンロード

Microsoft によって発売された無料で強力な IDE エディター

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール