GUN Cコンパイラ拡張文法学習ノート(3) インライン関数、組み込み関数、可変パラメータマクロ

1. インライン関数

1.1 属性宣言: noinline

  インライン関数に関連する 2 つの属性:noinlinealways_inlineこれら 2 つの属性の目的は、指定した関数をインライン展開するか展開しないかをコンパイル時にコンパイラに伝えることです。
ここに画像の説明を挿入
  inline を使用して宣言された関数はインライン関数と呼ばれ、通常、インライン関数の前には static および extern 変更が続きますinlineキーワードを使用したインライン関数の宣言はregister、キーワードを使用したレジスタ変数の宣言と同じであり、コンパイラがコンパイル時にインライン展開することを示唆するだけです。
  関数呼び出しの場合、一部の関数は短く簡潔で頻繁に呼び出され、呼び出しのオーバーヘッドが大きくコスト効率が悪い場合がありますが、現時点ではこの関数をインライン関数として宣言できます。コンパイラは、コンパイル中にインライン関数を検出すると、マクロのように呼び出しサイトでインライン関数を直接展開します。これにより、関数呼び出しのコストが削減されます。シーンや復元シーンを保存せずに、インライン関数によって展開されたコードが直接実行されます。

1.2 インライン関数とマクロ

  これを見て、「インライン関数にもマクロと同様の機能があるのに、インライン関数ではなくマクロを直接定義すればいいのでは?」と疑問に思う人もいるかもしれません。
  インライン関数にはマクロに比べて次のような利点があります。

  • パラメータ タイプ チェック: インライン関数はマクロの拡張特性を持っていますが、本質的には関数であることに変わりはありません。コンパイル プロセス中に、コンパイラはパラメータ チェックを実行できますが、マクロにはこの機能がありません。
  • デバッグが簡単: この関数でサポートされているデバッグ関数にはブレークポイント、シングル ステップなどが含まれており、インライン関数もサポートされています。
  • 戻り値: インライン関数には戻り値があり、呼び出し元に結果を返します。ANSI Cこの利点は、以前にステートメント式を使用して定義したマクロなど、マクロにも戻り値と型を含めることができるため、これに関連したものです。
  • インターフェイスのカプセル化: 一部のインライン関数を使用してインターフェイスをカプセル化できますが、マクロにはこの機能がありません。

1.3 コンパイラによるインライン関数の処理

  インライン関数を使用するとプログラムのサイズが大きくなりますが、ファイル内でインライン関数を複数回呼び出して複数回展開すると、プログラム全体のサイズが大きくなり、プログラムの実行効率がある程度低下します。コンパイラは実際の状況に応じて評価し、拡張する場合と拡張しない場合のメリットとデメリットを比較検討し、最終的に拡張するかどうかを決定します。コンパイラはインライン関数を展開するときに、ユーザー定義のインライン関数内にポインター、ループ、再帰があるかどうかを検出するだけでなく、関数の実行効率と関数呼び出しのオーバーヘッドの間のトレードオフも行います。
  一般に、プログラマの観点からインライン関数を拡張するかどうかを判断するには、主に次の要素が考慮されます。

  • 関数のサイズは小さいです。
  • 関数本体には、ポインターの代入、再帰、ループ、その他のステートメントはありません。
  • 頻繁な電話。
      関数のサイズが小さく、頻繁に呼び出されるため、インライン展開する必要があると考えられる場合、static inlineキーワードを使用して関数を変更できます。ただし、コンパイラは必ずしもインライン展開を行うわけではないため、展開する必要があるかどうかをコンパイラに明示的に指示したい場合は、関数を使用するnoinlinealways_inline属性宣言を行うことができます。

1.4 インライン関数がヘッダー ファイルで定義される理由

   質問:内联函数为什么要定义在头文件中呢?
   回答:因为它是一个内联函数,可以像宏一样使用,任何想使用这个内联函数的源文件,都不必亲自再去定义一遍,直接包含这个头文件,即可像宏一样使用。
   質問:为什么还要用static修饰呢?
   回答:因为我们使用inline定义的内联函数,编译器不一定会内联展开,那么当一个工程中多个文件都包含这个内联函数的定义时,编译时就有可能报重定义错误。而使用static关键字修饰,则可以将这个函数的作用域限制在各自的文件内,避免重定义错误的发生。

2. 内蔵機能

2.1 組み込み関数の定義

  組み込み関数とは、コンパイラ内に実装される関数です。これらの関数は、キーワードと同様に最初に宣言してから標準ライブラリ関数のように使用することなく、直接呼び出すことができます。
  組み込み関数の関数名。通常__builtinは で始まります。これらの関数は主にコンパイラ内部で、主にコンパイラのために使用されます。組み込み関数の主な用途は以下の通りです。

  • 可変長の引数リストを処理するために使用されます。
  • 異常なプログラム動作の処理、コンパイルの最適化、およびパフォーマンスの最適化に使用されます。
  • 関数の実行中に基礎となる情報、スタック情報などを表示します。
  • C標準ライブラリの共通関数を実装します。
      組み込み関数はコンパイラ内で定義され、主にコンパイラに関連するツールやプログラムによって呼び出されるため、これらの関数は文書化されておらず、頻繁に変更されるため、アプリケーション開発者にはこれらの関数の使用はお勧めできません。ただし、一部の関数は、プログラムの動作とコンパイルの最適化の基礎となるメカニズムを理解するのに非常に役立ちます。これらの関数は Linux カーネルでよく使用されるため、Linux で一般的に使用されるいくつかの組み込み関数を理解することが非常に必要です。カーネル。

2.2 よく使用される組み込み関数

  一般的に使用される主な組み込み関数は、__builtin_return_address() と __builtin_frame_address() の 2 つです。
  __builtin_return_address() の関数プロトタイプは次のとおりです。
ここに画像の説明を挿入
  この関数は、現在の関数または呼び出し元の戻りアドレスを返すために使用されます。関数のパラメータ LEVEL は、関数呼び出しチェーン内のさまざまなレベルの関数を示します。

● 0: 現在の関数の戻りアドレスを取得します。
● 1: 上位関数の戻りアドレスを取得します。
● 2: 上位関数の戻りアドレスを取得します。
●…

  もう 1 つの一般的に使用される組み込み関数__builtin_frame_address()、その関数プロトタイプは次のとおりです。
ここに画像の説明を挿入
  関数呼び出しの過程にはスタックフレームという概念もあります。関数が呼び出されるたびに、現在の関数のシーン (戻りアドレス、レジスタ、一時変数など) がスタックに保存され、関数呼び出しの各層で独自のシーン情報が独自のスタックに保存されますこのスタックは、現在の関数のスタック フレームです。各スタック フレームには、開始アドレスと終了アドレスがあります。多層関数呼び出しには、複数のスタック フレームがあります。各スタック フレームは、前のスタック フレームの開始アドレスを保存します。このようにして、各スタック フレームが呼び出しチェーンを形成します。
  組み込み関数 を通じて__builtin_frame_address(LEVEL)関数のスタック フレーム アドレスを確認します。
● 0: 現在の関数のスタック フレーム アドレスを表示します。
● 1: 上位関数のスタック フレーム アドレスを表示します。
●…

2.3 C標準ライブラリの組み込み関数

  GNU C コンパイラ内では、C 標準ライブラリの組み込み関数が、C 標準ライブラリ関数と同様のいくつかの組み込み関数を実装します。これらの関数はC標準ライブラリ関数の関数に似ており、関数名も同じですが、先頭にプレフィックスが追加されています__builtin
  C標準ライブラリの共通関数は以下のとおりです。
● メモリ関連の機能: memcpy()、memset()、memcmp()
● 数学関数: log()、cos()、abs()、exp()
● 文字列処理関数: strcat()、strcmp()、strcpy()、strlen()
● 印刷機能: printf()、scanf()、putchar()、puts()
  C標準ライブラリに対応した組み込み関数を使用することで、文字列のコピーや印刷も実現でき、C標準ライブラリ関数の機能を実現できます。

2.4 組み込み関数:__builtin_constant_p(n)

  この関数は主に、パラメータ n がコンパイル時に定数であるかどうかを判断するために使用されます。定数の場合、関数は 1 を返し、それ以外の場合、関数は 0 を返します。この関数は、コンパイル最適化のためのマクロ定義でよく使用されます。たとえば、マクロ定義は、マクロのパラメータが定数であるか変数であるかに応じて、さまざまな方法で実現できます。たとえば、次のカーネル ソース コードです。
ここに画像の説明を挿入ここに画像の説明を挿入

2.5 組み込み関数:__builtin_expect(exp,c)

  組み込み関数__builtin_expect()もコンパイルの最適化によく使用されますが、この関数には 2 つのパラメーターがあり、戻り値はパラメーターの 1 つであり、依然として ですexpこの組み込み関数の目的は、パラメーター exp の値が c である可能性が非常に高いことをコンパイラーに伝えることで、コンパイラーはこのヒントに基づいて分岐予測でコードの最適化を実行できるようになります。パラメータ c はこの関数の戻り値とは関係がありません。c がどのような値であっても、関数の戻り値は exp になります。主な用途は、コンパイラの分岐予測の最適化です
  現在、CPU 内には Cache キャッシュ デバイスがあります。CPU の実行速度は非常に高速ですが、外部 RAM の速度は比較的遅いため、CPU がメモリ RAM からデータを読み書きするときに、一定のパフォーマンスのボトルネックが発生します。プログラムの実行効率を向上させるために、CPU は通常、CPU の内部バッファであるキャッシュを介して特定の命令やデータをキャッシュします。CPU がメモリ データを読み書きするときは、まずキャッシュにアクセスして、それが可能かどうかを確認します。 be found: 見つかった場合は、直接読み取りと書き込みが行われます。見つからなかった場合は、Cache がデータの一部を再キャッシュします。CPU はメモリ RAM よりもはるかに高速にキャッシュの読み取りと書き込みを行うため、このキャッシュ方法によりシステムのパフォーマンスが向上します。
  それでは、Cache はどのようにしてメモリ データをキャッシュするのでしょうか? 簡単に言うと、空間的近接性の原理に基づいています。CPU が命令を実行している場合、CPU は通常、次のクロック サイクルで現在の命令の次の命令を高い確率で実行します。このとき、キャッシュが次の命令をキャッシュにキャッシュすると、CPU は次のクロック サイクルでキャッシュから命令を直接フェッチ、変換、実行できるため、計算効率が大幅に向上します。
  しかし、時には驚くこともあります。プログラムが実行中に関数呼び出し、分岐、goto ジャンプなどのプログラム構造に遭遇した場合、別の場所にジャンプして実行されます。キャッシュに元々キャッシュされている命令は、CPU によって実行される命令ではありません。このとき、キャッシュがミスしたといい、CPU が読み取れるように正しい命令コードをキャッシュが再キャッシュするのが、キャッシュの基本的な動作です。
  プログラムを書くと必ずif/switchなどの分岐を選択するプログラム構造が出てきますが、一般的には発生確率の高い分岐を先に書くことが推奨されます。**プログラムの実行中は、発生確率が高いため、ほとんどの場合ジャンプする必要がなく、プログラムはシーケンシャル構造と同等になり、キャッシュのキャッシュ ヒット率も大幅に向上します。プログラマーにプログラムを最適化するよう促すために、いくつかの関連マクロ (可能性が高いもの、可能性が低いものなど) がカーネルに実装されています。

2.6 Linux カーネルで起こりそうなこととありそうもないこと

  Linux カーネルでは、__builtin_expect() 組み込み関数を使用し、2 つのマクロを定義します。ここに画像の説明を挿入
  これら 2 つのマクロの主な機能は、特定の分岐が発生する確率が非常に高いか非常に低いか、または分岐が発生することは基本的に不可能であることをコンパイラに伝えることです。このヒント情報に基づいて、コンパイラはプログラムのコンパイル時に分岐予測の最適化を行います。
  これら 2 つのマクロの定義には、マクロのパラメータ x に対して 2 つの否定演算を実行するという詳細があります。つまり、パラメータ x をブール型に変換し、それを 1 および 0 と直接比較して、次のことを示します。 x が該当するコンパイラ それが真であるか偽である可能性が高いです。

3、可変パラメータマクロ

  可変パラメータ関数の定義と使用の基本ルーチンは、va_list、va_start、va_end、およびその他のマクロを使用して、それらの可変パラメータ リストを解析することです。GNU C は、これだけでは十分ではないと考えているため、もう 1 つの「神の支援」を提供します。単にマクロ定義が変数パラメータもサポートするだけです。

3.1 可変パラメータマクロ定義

  可変パラメータ マクロの実装形式は、実際には可変パラメータ関数の実装形式と似ています。可変パラメータ リストを表すには ... を使用します。可変パラメータ リストは不確実なパラメータで構成され、各パラメータはカンマで区切られます。次のプログラムに示すように。変数パラメーター マクロは、変数パラメーター関数のように変数パラメーター リストを解析するためにこれらのマクロを使用するのではなく、 C99 標準によって新たに追加された事前定義された__VA_ARGS__識別子を使用して、以前の変数パラメーター リストを表します。va_list、va_start、va_endプリプロセッサはマクロを展開するときに、__VA_ARGS__マクロ定義内のすべての識別子を変数パラメータ リストに置き換えます。
ここに画像の説明を挿入
  上記のプログラムはコンパイル時にエラーを報告し、構文エラーが発生します。これは、LOG マクロにパラメータを 1 つだけ渡し、変数パラメータが空であるためです。マクロを展開すると以下のようになります。
ここに画像の説明を挿入
  マクロ展開後、最初の文字列パラメータの後にカンマがあり、文法規則に準拠していないため、文法エラーが生成されます。このマクロの改善を続け、この文法上のエラーを回避するためにマクロ コネクタ ## を使用する必要があります。

3.2 改良版

  識別子 __VA_ARGS__ の前にマクロ コネクタ ## を追加しました。この利点は、変数パラメータ リストが空でない場合、## の機能は fmt と変数パラメータ リストを接続することであり、パラメータは次のように区切られます。カンマ 開くと、マクロは通常どおりに使用できます。可変パラメータ リストが空の場合、## には特別な目的があり、固定パラメータ fmt の後のカンマを削除して、マクロを通常どおりに使用できるようにします。ここに画像の説明を挿入ここに画像の説明を挿入

3.3 別の書き方

  可変パラメータマクロを定義する場合、可変パラメータリストを表す定義済み識別子 __VA_ARGS__ を使用するほかに、次のような記述方法も使用できます。
ここに画像の説明を挿入
  上記の形式は、GNU C 拡張機能を記述する新しい方法です。変数パラメータ リストを使用する代わりに、それを直接使用して変数パラメータ リストを表すことができ、後続のマクロ定義で args を直接使用して変数パラメータ リストを表すことができます__VA_ARGS__args...変数パラメータ リストが空の場合の構文エラーを回避するには、パラメータ間にコネクタを追加する必要もあります##
ここに画像の説明を挿入

3.4 カーネル内の可変個引数マクロ

  可変パラメータ マクロは、主にカーネルでのログ出力に使用されます。一部のドライバー モジュールまたはサブシステムは、印刷スイッチ、印刷形式、優先制御などの機能をサポートする独自の印刷マクロを定義する場合があります。

ここに画像の説明を挿入ここに画像の説明を挿入
  このマクロは 3 つのバージョンを定義します。カーネルのコンパイル時に動的デバッグ オプションがある場合、このマクロは Dynamic_pr_debug として定義されます。動的デバッグ オプションが構成されていない場合は、DEBUG マクロを使用してこのマクロの開始と終了を制御できます。
  no_printk() は、インライン関数として printk.h ヘッダー ファイルに定義されており、format 属性宣言を通じて、printf 標準に従ってパラメーターの形式をチェックするようにコンパイラーに指示します。
  最も興味深いのは、dynamic_pr_debug マクロです。マクロ定義は do{…}while(0) 構造を採用しています。これは冗長に思えるかもしれません。マクロはこれを使用しても使用しなくても機能します。とにかく、一度実行されるのに、なぜこの一見「余分な」ループ構造を使用するのでしょうか? 理由は実は非常に単純で、条件や選択などの分岐構造の文において、マクロ展開後のマクロの曖昧さを防ぐためです。

おすすめ

転載: blog.csdn.net/qq_41866091/article/details/130556039