Cプログラミング/プリプロセッサディレクティブとマクロ

ディレクティブは、プリプロセッサ(プリプロセッサディレクティブ)またはコンパイラー(コンパイラーディレクティブ)に対して、ソースコードの一部またはすべてを処理する方法、または最終オブジェクトにフラグを設定する方法を指示する特別な命令であり、ソースコードの記述を容易にし(例えば、より移植性が高く)、ソースコードをより理解しやすくするために使用されます。 プリプロセッサは、コンパイラによって呼び出される独立したプログラムか、コンパイラ自体の一部です。

#includeEdit

Cには、言語の一部としていくつかの機能があり、標準ライブラリの一部としていくつかの機能があります。 Cコンパイラがプログラムをコンパイルするとき、通常は標準Cライブラリにもリンクします。 たとえば、#include <stdio.h>ディレクティブが検出された場合、ディレクティブはstdioの内容に置き換えられます。hヘッダーファイル。ライブラリの機能を使用する場合、Cでは使用するものを宣言する必要があります。 プログラムの最初の行は、次のようになる前処理ディレクティブです。

#include <stdio.h>

上記の行は、stdioにあるC宣言を引き起こします。あなたのプログラムで使用するために含まれるhヘッダー。 通常、これはstdioと呼ばれるヘッダーファイルの内容をプログラムに挿入するだけで実装されます。hは、システムに依存する場所にあります。 このようなファイルの場所は、コンパイラのドキュメントに記載されている場合があります。 標準のCヘッダーファイルのリストは、以下のヘッダーテーブルに記載されています。

stdio。hヘッダには、ストリームと呼ばれるI/Oメカニズムの抽象化を使用して、入出力(I/O)のためのさまざまな宣言が含まれています。 たとえば、標準出力にテキストを出力するために使用されるstdoutと呼ばれる出力ストリームオブジェクトがあり、通常はコンピュータ画面にテキストを表

上の例のように山括弧を使用する場合、プリプロセッサは、標準インクルードファイルを開発環境パスに沿って検索するように指示されます。

#include "other.h"

引用符(“”)を使用すると、プリプロセッサは、ヘッダーファイルの追加の、通常はユーザー定義の場所を検索し、それらの追加の場所に見つからな この形式では、#includeディレクティブを含むファイルと同じディレクトリ内の検索を含めるのが一般的です。

注:#includeディレクティブのベンダー固有の実装については、使用している開発環境のドキュメントを確認する必要があります。

HeadersEdit

The C90 standard headers list:

  • <assert.h>
  • <ctype.h>
  • <errno.h>
  • <float.h>
  • <limits.h>
  • <locale.h>
  • <math.h>
  • <setjmp.h>
  • <signal.h>
  • <stdarg.h>
  • <stddef.h>
  • <stdio.h>
  • <stdlib.h>
  • <string.h>
  • <time.h>

Headers added since C90:

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <iso646.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>
  • <wchar.h>
  • <wctype.h>

#pragmaEdit

pragma(pragmatic information)ディレクティブは標準の一部ですが、任意のプラグマの意味は、標準のソフ使用される。 #Pragmaディレクティブは、コンパイラに特別な動作を要求する方法を提供します。 このディレクティブは、異常に大きいプログラムや、特定のコンパイラの機能を利用する必要があるプログラムに最も便利です。

プラグマはソースプログラム内で使用されます。

#pragma token(s)
  1. プラグマの後には、通常、コンパイラが従うコマンドを表す単一のトークンが続きます。 サポートされているトークンのリストについては、使用するC標準のソフトウェア実装を確認する必要があります。 当然のことながら、#pragmaディレクティブに表示できるコマンドのセットはコンパイラごとに異なります。

たとえば、最も実装されているプリプロセッサディレクティブの一つである#pragma onceヘッダーファイルの先頭に配置された場合、プリプロセッサによって数回インクルードされた場合、そのファイルが存在するファイルがスキップされることを示します。

注意:このアクションを行うための他の方法が存在し、これは一般的にincludeガードを使用すると呼ばれています。

#defineEdit

警告:プリプロセッサマクロは魅力的ですが、正しく行われないと予期しない結果を生成する可能性があります。 マクロは、何かがコンパイルされる前にソースコードに行われるテキスト置換であることに常に注意してくださ コンパイラはマクロについて何も知らず、決してそれらを見ることはありません。 これは、他の負の効果の中で、あいまいなエラーを生成することができます。 同等のものがある場合は、言語機能を使用することをお勧めします(例では、enum#defined定数)。つまり、マクロが非常に便利な場合があります(例については、以下のdebugマクロを参照してください)。

#defineディレクティブは、プリプロセッサがコンパイル前にプログラムのソースコードを操作するために使用する値またはマクロを定義するために使 コンパイラがソースコードに作用する前にプリプロセッサ定義が置換されるため、#defineによって導入されたエラーはトレースするのが困難です。 規則により、#defineを使用して定義された値は大文字で指定されます。 そうすることは要件ではありませんが、それ以外のことを行うことは非常に悪い習慣と考えられています。 これにより、ソースコードを読み取るときに値を簡単に識別することができます。

今日、#defineは主にコンパイラとプラットフォームの違いを処理するために使用されています。 たとえば、defineは、システムコールの適切なエラーコードである定数を保持する可能性があります。 したがって、#defineの使用は、絶対に必要な場合を除き制限する必要があります。typedef文と定数変数は、多くの場合、同じ関数をより安全に実行できます。

#defineコマンドのもう一つの特徴は、引数を取ることができることであり、擬似関数の作成者としてはかなり便利です。 次のコードを考えてみましょう:

#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}

複雑なマクロを使用するときは、余分な括弧を使用することをお勧めします。 上記の例では、変数”x”は常にそれ自身の括弧のセット内にあることに注意してください。 このようにして、0と比較されるか、または-1で乗算される前に、全体で評価されます。 また、マクロ全体が他のコードによって汚染されるのを防ぐために、括弧で囲まれています。 注意しないと、コンパイラがコードを誤って解釈する危険があります。

副作用のため、上記のようにマクロ関数を使用することは非常に悪い考えと考えられています。

int x = -10;int y = ABSOLUTE_VALUE( x++ );

ABSOLUTE_VALUE()が実際の関数であった場合、’x’の値は’-9’になりましたが、マクロの引数であったため、2回展開され、値は-8になりました。

例:

マクロの危険性を説明するために、この素朴なマクロを考えてみましょう

#define MAX(a,b) a>b?a:b

コード

i = MAX(2,3)+5;j = MAX(3,2)+5;

これを見て、実行後の値 文は

int i = 2>3?2:3+5;int j = 3>2?3:2+5;

したがって、実行後にi=8とj=3の代わりにi=j=8の期待される結果! これが、上記の括弧の余分なセットを使用するように警告された理由ですが、これらであっても、道路は危険に満ちています。 アラートリーダーは、abに式が含まれている場合、定義は次のように、マクロ定義内のa、bのすべての使用を括弧で囲む必要があることをすぐに認識するかもしれません。

#define MAX(a,b) ((a)>(b)?(a):(b))

これは、a、bに副作用がない場合に機能します。 実際、

i = 2;j = 3;k = MAX(i++, j++);

はk=4、i=3、j=5になります。 これは、MAX()が関数のように動作することを期待している人にとっては非常に驚くべきことです。正しい解決策は何ですか?

解決策は、マクロをまったく使用しないことです。 このようなグローバルなインライン関数

inline int max(int a, int b) { return a>b?a:b }

上記の落とし穴はありませんが、すべての型で動作するわけではありません。注:コンパイラは関数をインライン化できるため、定義がヘッダーファイルにない限り、明示的なinline宣言は実際には必要ありません(gccでは-finline-functions-O3inlineinlineは、関数が複数の翻訳単位で定義されているため、エラーメッセージを生成せずにヘッダーファイルで関数を定義できるようにするために必要です)。

(#,##)

#演算子と##演算子は#defineマクロで使用されます。 #を使用すると、#の後の最初の引数が引用符で囲まれた文字列として返されます。 たとえば、コマンド

#define as_string( s ) # s

コンパイラはこのコマンドを

puts( as_string( Hello World! ) ) ;

puts( "Hello World!" );

##を使用して##の前にあるものを##の後にあるものと連結します。 たとえば、コマンド

#define concatenate( x, y ) x ## y...int xy = 10;...

コンパイラは

printf( "%d", concatenate( x, y ));

printf( "%d", xy);

もちろん、標準出力に10を表示します。

マクロ引数に定数接頭辞または接尾辞を連結して、

#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )

のように有効な識別子を取得することができます。my_bar()という関数を定義します。 しかし、連結演算子を使用してマクロ引数を定数文字列に統合することはできません。 このような効果を得るために、2つ以上の連続した文字列定数が検出されたときに単一の文字列定数と同等であると見なされるANSI Cプロパティを使 このプロパティを使用すると、マクロプロセッサが

#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )

と書くことができます。

puts( "I'm eating " "fruit" " today." )

これはcパーサーによって単一の文字列定数として解釈されます。次のトリックは、数値定数を文字列リテラルに変換するために使用できます

#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));

これは2つのステップで展開されるので、少しトリッキー 最初のnum2str(CONST)str(23)"23"に置き換えられます。 これは、次の例で役立ちます:p>

#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif

これは、ファイルとメッセージが発行された行を含む素敵なデバッグメッセージを提供します。 ただし、DEBUGが定義されていない場合、デバッグメッセージはコードから完全に消えます。 これは、コンパイルパラメータに応じて表示されたり消えたりするバグにつながる可能性があるためです。

macrosEdit

マクロは型チェックされないため、引数は評価されません。 また、スコープには適切に従いませんが、渡された文字列を取得し、マクロのテキスト内のmacro引数の各出現箇所をそのパラメータの実際の文字列に置き換

マクロの使用方法の例:

 #include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }

–“a”の結果は”2″でなければなりません(b+c=16->ADD->16/SLICES->16/SLICES->16/SLICES->16/SLICES->16/SLICES->>結果は”2″です)

注:
ヘッダーでマクロを定義するのは通常悪い習慣です。

マクロは、関数や他のメカニズムで同じ結果を達成することができない場合にのみ定義する必要があります。 いくつかのコンパイラは、小さな関数への呼び出しがインラインコードに置き換えられるようにコードを最適化することができ、可能な速度の利点Typedef、enums、およびinline(C99で)を使用する方が良いオプションです。

インライン関数が機能しないいくつかの状況の1つ-関数のようなマクロを代わりに使用することを余儀なくされている-コンパイル時定数(構造体の静的初期化)を初期化することです。これは、マクロへの引数がコンパイラが別のリテラルに最適化できるリテラルである場合に発生します。

#errorEdit

#errorディレクティブはコンパイルを停止します。 標準では、コンパイラがディレクティブ内の残りのトークンを含む診断を発行するように指定しています。 これは主にデバッグ目的で使用されます。

プログラマは、条件付きブロック内で”#error”を使用して、ブロックの先頭にある”#if”または”#ifdef”がコンパイル時の問題を検出したときにすぐにコンパイラを停止通常、コンパイラはブロック(およびその中の”#error”ディレクティブ)をスキップし、コンパイルが続行されます。

 #error message

#warningEdit

多くのコンパイラは#warningディレクティブをサポートしています。 検出されると、コンパイラはディレクティブ内の残りのトークンを含む診断を出力します。

 #warning message

#undefEdit

#undefディレクティブはマクロを定義しません。 識別子は以前に定義されている必要はありません。

#if,#else,#elif,#endif(conditionals)Edit

#ifコマンドは、制御する条件式がゼロまたは非ゼロに評価されるかどうかをチェックし、それぞれコードブロックを除外または含 たとえば、次のようにします。

 #if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif

条件式には、代入演算子、インクリメント演算子とデクリメント演算子、アドレスオブ演算子、およびsizeof演算子を除く任意のC演算子を含めることができます。

前処理で使用される一意の演算子は、定義された演算子ではありません。 マクロ名(オプションでかっこで囲まれたもの)が現在定義されている場合は1を返し、定義されていない場合は0を返します。#endifコマンドは、#if#ifdef#ifndefで開始されたブロックを終了します。#elifコマンドは#ifと似ていますが、一連のコードブロックから1つを抽出するために使用されます。 例:

 #if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */

#ifdef,#ifndefEdit

#ifdefコマンドは#ifと似ていますが、マクロ名が定義されている場合は、それに続くコードブロックが選択されます。 この点で、

#ifdef NAME

#if defined NAME

#ifndefコマンドは#ifdefに似ていますが、テストが逆になっています。

#ifndef NAME

#ifndef NAME

#if defined NAME

#ifndef NAME

#ifndef NAME

/p>

#if !defined NAME

#lineedit

このプリプロセッサディレクティブは、ディレクティブの後の行のファイル名と行番号を新しい値に設定するため これは、__FILE__および__LINE__マクロを設定するために使用されます。

コメントを残す

メールアドレスが公開されることはありません。