このマクロは GCC をクラッシュさせますか?読めば答えがわかります
この記事の目的は、C のマクロの壮大な世界を紹介することです。
C では、# で始まる行は、ソース ファイルのコンパイル時にコンパイラによって解釈されます。これらはプリプロセッサ ディレクティブと呼ばれます。マクロもその 1 つです。
ちょっとした歴史上のポイント:
C 言語マクロは、ANSI C (または C89) と呼ばれる最初の C 言語標準で導入されました。
これは、1989 年に米国規格協会 (ANSI) によって標準化されました。ただし、この標準化の前に、マクロは 1970 年代に使用されていた古典的な C (または K&R C) 言語の一部でした。
UNIX オペレーティング システム用にデニス リッチーによって開発されたオリジナルの C コンパイラには、プリプロセッサを介した初歩的な形式のマクロが既に組み込まれており、#define による定義が可能です。
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
定義の動作は非常に簡単に理解できます。コンパイラはコード内のすべての出現箇所を定義された値に置き換えます。これは、次の構文 #define
「Ctrl+F と置換」に少し似ています。
define を使用して、コード内で使用できる関数を定義できます。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
条件付きでマクロを宣言できます。
名前がすでに定義されている場合は、次のコードを実行します。
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
この場合、#ifndef を使用しますが、次のものも存在します。
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
私たちは、#if の終わりを一括コメントで知らせたいと考えています。これは、コードをより適切にナビゲートできるようにするための規則です。
前の例では、キーワード __FUNCTION__ と __LINE__ を使用していることがわかります。
ご想像のとおり、これらはコンパイラが正しい値に置き換えるマクロです。
共通の定義済みマクロのリストがあります。
いわゆるシステム固有のマクロがあることに注意してください。
すべてを網羅しているわけではない小さなリスト:
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
ここでは、特にログの作成時に役立つ可変個引数マクロを生成していることがわかります。
(printfs でログを作成するのは得策ではないとしても)
このためには、規則はありませんが、多くの場合 *.def という名前の外部ファイルを作成する必要があります。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
この種のマクロは非常に便利です。正直、これがソース コード内で見つかることはほとんどありませんが、これを使用すると、ソース コードを変更せずにプログラムの動作を変更できます。面白いことに、これはカーネルの作成によく使用されます。 IDT や GDT などのグローバル構造を生成できます。
注意: まず簡単に説明しますが、マクロは優れたツールですが、注意が必要です。このタイプのマクロは絶対に使用しないでください:
#if (X == 1) #define Y 2 #elif (X == 2) #define Y "Ami de la bonne blague, bonjour !" #else #define Y NULL #endif /* ! X */ /* ... */ int main(void) { #if (X == 1) printf("%d\n", Y); #elif (X == 2) printf("%s\n", Y); #else printf("%p\n", Y); #endif /* ! X */ }
例を見てみましょう: MIN(2 5, fibo(25))
MIN(2 5, fibo(25)) => (2 5
ここで問題となるのは計算の優先順位です。コンパイラは最初に比較を実行し、次に加算を実行するため、2 (1) となります。これを修正するには、マクロ引数を使用してかっこを追加します。
// Ici, l'opérateur ## est l'opérateur de concaténation #define DEBUG_PRNTF(fmt, ...) printf("LOG" ## fmt, __VA_ARGS__);
ユーザーがパラメータとして何を渡すかは決して分からないため、引数には常に括弧を付けてください。
MIN(2 5, fibo(25)) => (2 5
コンパイラーが愚かで厄介な置換を行っていることに気付きました。これは、fibo(25) を 2 回計算することを意味します。再帰的な実装かどうかはご想像にお任せします。
この問題を解決するには、if の前に中間変数を宣言します。
// color.def X(NC, "\e[0m", "No Color", 0x000000) X(BLACK, "\e[0;30m", "Black", 0x000000) X(GRAY, "\e[1;30m", "Gray", 0x808080) X(RED, "\e[0;31m", "Red", 0xFF0000) X(LIGHT_RED, "\e[1;31m", "Light Red", 0xFF8080) X(GREEN, "\e[0;32m", "Green", 0x00FF00) X(LIGHT_GREEN, "\e[1;32m", "Light Green", 0x80FF80) X(BROWN, "\e[0;33m", "Brown", 0xA52A2A) X(YELLOW, "\e[1;33m", "Yellow", 0xFFFF00) X(BLUE, "\e[0;34m", "Blue", 0x0000FF) X(LIGHT_BLUE, "\e[1;34m", "Light Blue", 0xADD8E6) X(PURPLE, "\e[0;35m", "Purple", 0x800080) X(LIGHT_PURPLE, "\e[1;35m", "Light Purple", 0xEE82EE) X(CYAN, "\e[0;36m", "Cyan", 0x00FFFF) X(LIGHT_CYAN, "\e[1;36m", "Light Cyan", 0xE0FFFF) X(LIGHT_GRAY, "\e[0;37m", "Light Gray", 0xD3D3D3) X(WHITE, "\e[1;37m", "White", 0xFFFFFF)
ここでは、単なる遊びのための過剰なコードです。これらのマクロをコード内で使用することを必ずしも推奨するわけではありません。
私はただ楽しんでいます (人生において良いことです)。
typedef struct { const char *name; const char *ansi_code; const char *description; unsigned int rgb; } Color; #define X(NAME, ANSI, DESC, RGB) { #NAME, ANSI, DESC, RGB }, Color colors[] = { #include "color.def" }; #undef X #define X(NAME, ANSI, DESC, RGB) printf("%s (%s) = %s\n", #NAME, DESC, #RGB); void print_colors() { // Bien entendu, on pourrait itérer sur la structure créée mais c'est une illustration #include "color.def" } #undef X
少し -fsanitize=address を付けてテストしてみましょう。本当にクレイジーだよ。スイッチを行うために構造体の名前の文字列をパラメータとして受け取る auto_free 関数の改善も見られます。
関数の実行時間を計算するだけの追加の冷却関数。ベンチマークに非常に役立ちます。
#define SENS_DE_LA_VIE 3.14 /* ... */ printf("%f\n", SENS_DE_LA_VIE);
マクロを引数として受け取り、それを展開する小さな X マクロ。
#define INC(a) a++ #define MULTI_LINE(a,b) a = b; \ b = 0; INC(my_variable); MULTI_LINE(my_variable, foobar) // Je souligne le fait qu'il peut ne pas y avoir de ';' en fin de ligne // Cela donnera my_variable++; my_variable = foobar; foobar = 0;
C には制限がないため、ここでは実際にマクロを使用して関数全体を生成します。私もですか?
#ifdef DEBUG // Je souligne qu'il est rarement conseillé d'utiliser des printf() en debug // et que nous avons brisé la règle du nom des macros en MAJ. #define return printf("(%s:%d)\n", __FUNCTION__, __LINE__); return #endif /* ! DEBUG */ int main(void) { return 1; }
ここで話をまとめます。本当に素晴らしいものをたくさん見ました。誘惑に駆られた場合は、ご自身でマクロを自由に発見してください。まだまだ見どころはたくさんあります。
結論としては、RTFM.
追記: タイトルに関しては、マクロは再帰的ではなく、深さ 1 でのみ展開されます。現在のケースでは、GCC は INC で implicit_declaration を作成し、クラッシュします。
以上が#define INC(a) INC(a ?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。