この章では、プラグマについて説明します。「プラグマ」とは、コンパイラに特定の情報を渡すために使用するコンパイラ指令です。プラグマを使用すると、コンパイル内容を詳細に渡って制御できます。たとえば、pack プラグマを使用すると、構造体の中のデータの配置を変えることができます。プラグマは「指令」とも呼ばれます。
プラグマは、C++ 標準の一部ではありません。
#pragma keyword #pragma keyword (a [ , a] ... )[ , keyword ( a [, a ] ... )] ,... #pragma sun keyword
変数 keyword は特定の指令を示し、a は引数を示します。
CC が認識する一般的な指令を次に示します。
align - デフォルトを無効にして、パラメータ変数のメモリー境界を指定したバイト境界に揃えます。
init - 指定した関数を初期化関数にします。
fini - 指定した関数を終了関数にします。
ident - 実行可能ファイルの .comment 部に、指定した文字列を入れます。
pack (n) - 構造体オフセットの配置を制御します。n の値 は、すべての構造体メンバーに合った最悪の場合の境界整列を指定する数字で、0、1、2、4、8 のいずれかにします。
unknown_control_flow - 手続き呼び出しの通常の制御フロー属性に違反するルーチンのリストを指定します。
weak - 弱いシンボル結合を定義します。
#pragma align integer (variable [,variable]...)
align を使用すると、指定したすべての変数 variable (変数) のメモリー境界を integer (整数) バイト境界に揃えることができます (デフォルト値より優先されます) 。ただし、次の制限があります。
integer は、1 ‾ 128 の範囲にある 2 の二乗、つまり、1、2、4、8、16、32、64、128 のいずれかでなければなりません。
variable には、大域変数か静的変数を指定します。局所変数またはクラスメンバー変数は指定できません。
指定されたメモリーの境界値がデフォルト値より小さいと、デフォルト値が使用されます。
この #pragma 行は、指定した変数の宣言より前になければなりません。前にないと、この #pragma 行は無視されます。
この #pragma 行で指定されていても、プラグマ行に続くコードの中で宣言されない変数は、すべて無視されます。次に、正しく宣言されている例を示します。
#pragma align 64 (aninteger, astring, astruct) int aninteger; static char astring[256]; struct S {int a; char *b;} astruct;
#pragma init (identifier [,identifier]...)
init を使用すると、identifier (識別子) を「初期設定関数」にします。この関数は void 型で、引数を持ちません。この関数は、実行開始時にプログラムのメモリーイメージを構築する時に呼び出されます。共有オブジェクトの初期設定子の場合、共有オブジェクトをメモリーに入れるとき、つまりプログラムの起動時または dlopen() のような動的ロード時のいずれかに実行されます。初期設定関数の呼び出し順序は、静的と動的のどちらの場合でもリンカーが処理した順序になります。
ソースファイル内で #pragma init で指定された関数は、そのファイルの中にある静的コンストラクタの後に実行されます。identifier は、この #pragma で指定する前に宣言しておく必要があります。
#pragma fini (identifier [,identifier]...)
fini を使用すると、identifier (識別子) を「終了関数」にします。この関数は void 型で、引数を持ちません。この関数は、プログラム制御によってプログラムが終了する時、または関数内の共有オブジェクトがメモリーから削除されるときに呼び出されます。初期設定関数と同様に、終了関数はリンカーが処理した順序で実行されます。
ソースファイル内で #pragma fini で指定された関数は、そのファイルの中にある静的デストラクタの後に実行されます。identifier は、この #pragma で指定する前に宣言しておく必要があります。
#pragma ident string
ident を使用すると、実行可能ファイルの .comment 部に、string に指定した文字列を記述することができます。
# pragma pack ([n])
pack は、構造体メンバーの配置制御に使用します。
n を指定する場合、0 であるか 2 の累乗でなければなりません。0 以外の値を指定すると、コンパイラは n バイトの境界整列と、データ型に対するプラットフォームの自然境界のどちらか小さい方を使用します。たとえば次の指令は、自然境界整列が 4 バイトまたは 8 バイト境界である場合でも、指令の後 (および後続の pack 指令の前) に定義されているすべての構造体のメンバーを 2 バイト境界を超えないように揃えます。
#pragma weak pack(2)
n が 0 であるか省略された場合、メンバー整列は自然境界整列の値に戻ります。
n の値がプラットフォームの最も厳密な境界整列と同じかそれ以上の場合には、自然境界整列になります。
表 3-1 プラットフォームの最も厳密な境界整列
プラットフォーム |
最も厳密な境界整列 |
---|---|
x86 |
4 |
SPARC 一般、v7、v8、v8a、v8plus、v8plusa |
8 |
SPARC v9、v9a |
16 |
pack 指令は、次の pack 指令までに存在するすべての構造体定義に適用されます。別々の翻訳単位にある同じ構造体に対して異なる境界整列が指定されると、プログラムは予測できない状態で異常終了する場合があります。特に、コンパイル済みライブラリのインタフェースを定義するヘッダーをインクルードする場合は、その前に pack を使用しないでください。プログラムコード内では、pack 指令は境界整列を指定する構造体の直前に置き、#pragma pack( ) は構造体の直後に置くことをお勧めします。
#pragma unknown_control_flow (name,[,name]...)
unknown_control_flow を使用すると、手続き呼び出しの通常の制御フロー属性に違反するルーチンの名前のリスト name,[,name]... を指定できます。たとえば、setjmp()の直後の文は、他のどんなルーチンを呼び出してもそこから返ってくることができます。これは、longjmp() を呼び出すことによって行います。
このようなルーチンを使用すると標準のフローグラフ解析ができないため、呼び出す側のルーチンを最適化すると安全性が確保できません。このような場合に #pragma unknown_control_flow を使用すると安全な最適化が行えます。
#pragma weak function-name1 [=function-name2]
function-name1 は関数名を、function-name2 は複数の関数を指定する場合の 2 つ目の関数名を示します。
weak を使用すると、弱い (weak) 大域シンボルを定義できます。このプラグマは主にソースファイルの中でライブラリを構築するために使用されます。リンカーは弱いシンボルを認識できなくてもエラーメッセージを出しません。
次の指令は、bar を弱いシンボルとして定義しています。リンカーが bar という関数の定義を見つけられなくても、エラーメッセージは生成されません。
#pragma weak bar
次の指令は、プログラム内の任意の位置に bar が定義されている場合は bar に対するすべての参照を解決し、bar が定義されていない場合は foo に対するすべての参照を解決するようにリンカーに指示します。
#pragma weak bar = foo
weak プラグマ内で関数を使用するには、使用前にその関数の宣言を行う必要があります。次に例を示します。
extern void bar(int) extern void _bar(int) #pragma weak _bar=bar
#pragma weak を使用する場合の効果は次のとおりです。
プログラムで function_name1 を呼び出すだけで定義していない場合、リンカーはライブラリ中の定義を使用します。
プログラムで独自の function_name1 を定義している場合は、プログラム中の定義が使用されるため、ライブラリの中にある function_name1 の弱い大域定義は使用されません。
プログラムが直接 function_name2 を呼び出す場合は、ライブラリ中の定義が使用され、 function_name2 を重複定義するとエラーになります。
詳細は、Solaris のマニュアル『リンカーとライブラリ』を参照してください。
プラグマ内の名前は、リンカーが認識できるものでなければなりません。つまり、関数に C++ リンケージがある場合には「符号化された」名前でなければなりません。