GNU Cは、
ANSIの標準Cにはない特徴的な言語機能をいくつか提供しています
(`-pedantic'オプションは、
これらの機能が使われた場合に
警告メッセージを出力するようGNU CCに対して指示するものです)。
条件コンパイルにおいて、
これらの機能が利用可能であるかどうかをテストするためには、
__GNUC__
というマクロが事前に定義されているかどうかをチェックします。
この__GNUC__
というマクロは、
GNU CCでは常に定義されています。
これらの拡張機能はCとObjective Cで利用可能です。 また、 そのほとんどはC++でも利用可能です。 C++にのみ適用可能な拡張機能については、 C++言語に対する拡張機能を参照してください。
GNU Cにおいては、 丸括弧()で囲まれた複文を1つの式とみなすことができます。 これによって、 1つの式の中でループ、 switch文、 ローカル変数を使うことができます。
複文とは波括弧{}で囲まれた一連の文であるということを思い出してください。 この複文においては、 丸括弧()が波括弧{}のまわりを囲むことになります。 例えば、
({ int y = foo (); int z; if (y > 0) z = y; else z = - y; z; })
は、
foo ()
の戻り値の絶対値を表わす式としては
(やや必要以上に複雑ではありますが)
正当なものです。
複文の末尾には、
式の後ろにセミコロンが続いたものがなければなりません。
この部分式の値が、
式全体の値となります
(波括弧{}で囲まれた部分の末尾に異なる種類の文を使うと、
式全体の型はvoid
となり、
事実上、
値を持たないことになります)。
この機能は、 マクロ定義を「安全」な (したがって、 個々のオペランドをただ1回のみ評価する) ものにするのに特に役立ちます。 例えば標準Cでは、 「最大値を返す」関数は、 以下に示すようにマクロとして定義するのが一般的です。
#define max(a,b) ((a) > (b) ? (a) : (b))
しかし、
この定義は、
aまたはbのいずれか一方を2回評価するので、
オペランドが副作用を持つ場合には望ましくない結果をもたらします。
オペランドの型が分かっている場合
(ここではint
であると仮定しましょう)、
GNU Cでは、
このマクロを以下に示すように安全に定義することができます。
#define maxint(a,b) \ ({int _a = (a), _b = (b); _a > _b ? _a : _b; })
列挙定数の値、 ビット・フィールドのビット幅、 静的変数の初期値などの定数式の中に、 文を組み込むことはできません。
オペランドの型が分からない場合でも、
複文を1つの式に組み込むことは可能です。
しかしその場合には、
typeof
(typeof
による型への参照を参照)、
または、
型の名前付け (式の型に対する名前付けを参照)を使わなければなりません。
個々の複文式(statement expression)の中で局所ラベルを宣言すると、
その複文式自体がその局所ラベルのスコープになります。
局所ラベルは単なる識別子です。
通常のgoto
文によってそこへジャンプすることができますが、
このようなジャンプは、
その局所ラベルが属する複文式の中からのみ可能です。
局所ラベルの宣言は、
__label__ label;
あるいは
__label__ label1, label2, ...;
のように行います。 局所ラベルの宣言は、 複文式の先頭、 すなわち`({'の直後、 かつ、 通常の宣言のどれよりも前になければなりません。
ラベルの宣言はラベルの名前を定義するもので、
ラベル自体を定義するわけではありません。
ラベルの定義は、
通常どおりlabel:
によって、
複文式に含まれる複数の文の中のどこかで行わなければなりません。
局所ラベル機能が役に立つのは、
複文式がマクロの中でしばしば使われるからです。
あるマクロの中に入れ子になったループがあると、
そのループから抜け出るのにgoto
が役に立ちます。
しかし、
スコープが関数全体である通常のラベルを使うことはできません。
そのマクロが1つの関数の中で数回展開されることがあると、
同じ関数の中でそのラベルが複数回定義されることになるからです。
局所ラベルはこのような問題を回避してくれます。
以下にその例を示します。
#define SEARCH(array, target) \ ({ \ __label__ found; \ typeof (target) _SEARCH_target = (target); \ typeof (*(array)) *_SEARCH_array = (array); \ int i, j; \ int value; \ for (i = 0; i < max; i++) \ for (j = 0; j < max; j++) \ if (_SEARCH_array[i][j] == _SEARCH_target) \ { value = i; goto found; } \ value = -1; \ found: \ value; \ })
カレントな関数
(あるいは、
そのラベルを含んでいる関数)
の中で定義されたラベルのアドレスを、
単項演算子`&&'で獲得することができます。
この値の型はvoid *
です。
この値は定数であり、
void *
型の定数が正当であるところではどこでも使うことができます。
以下に例を示します。
void *ptr; ... ptr = &&foo;
この値を使うためには、
そのラベルにジャンプすることができる必要があります。
これは評価goto(computed goto)文
(15)
goto *exp;
で行います。
以下に例を示します。
goto *ptr;
void *
型の任意の式を使うことができます。
このような定数の用途の1つに、 ジャンプ・テーブルとして機能する静的配列の初期化があります。
static void *array[] = { &&foo, &&bar, &&hack };
こうすると、 以下のようにインデックスを使ってラベルを選択することができます。
goto *array[i];
ここで、 配列の添字が上限内にあるかどうかチェックされないことに注意してください。 Cにおける配列のインデックスでは、 このようなチェックは決して行われません。
このようなラベル値の配列は、
switch
文が目的とするところと同様の効果を持ちます。
switch
文のほうがよりすっきりしていますので、
実現しようとしていることがswitch
文にうまく適合しない場合以外は、
ラベル値の配列ではなく、
switch
文を使うようにしてください。
ラベル値の別の用途として、 インタープリタの中で、 スレッド化されたコードを対象にして使うという方法があります。 非常に高速なディスパッチ処理を実現するために、 インタープリタ関数内のラベルの値をスレッド化されたコードの中に持たせることができます。
別の関数の中のコードにジャンプするために、 このメカニズムを使うことができます。 しかし、 このようなことを行うと、 まったく予想不可能な事態が発生するでしょう。 これを回避する最良の方法は、 ラベルのアドレスを自動変数にのみ格納して、 決して引数として渡さないということです。
入れ子関数(nested function)は、
別の関数の内部に定義された関数です
(入れ子関数はGNU C++ではサポートされていません)。
入れ子関数の名前は、
それが定義されたブロックの内部に局所的なものです。
例えば以下では、
square
という名前の入れ子関数を定義して、
それを2回呼び出しています。
foo (double a, double b) { double square (double z) { return z * z; } return square (a) + square (b); }
入れ子関数を包含している関数が持つ変数のうち、
入れ子関数が定義された箇所において可視なものすべてに対して、
入れ子関数の中からアクセスすることができます。
これを構文スコーピング(lexical scoping)と呼びます。
ここでは例として、
offset
という名前を持つ、
継承された変数を使う入れ子関数を示します。
bar (int *array, int offset, int size) { int access (int *array, int index) { return array[index + offset]; } int i; ... for (i = 0; i < size; i++) ... access (array, i) ... }
関数の中で変数定義ができるところであればどこでも、 入れ子関数の定義を行うことができます。 つまり、 任意のブロック内で、 そのブロックの最初の文の前であれば、 入れ子関数の定義が可能です。
入れ子関数のアドレスをどこかに格納したり別の関数へ渡したりすることによって、 入れ子関数の名前が有効なスコープの外部からでも、 入れ子関数を呼び出すことができます。
hack (int *array, int size) { void store (int index, int value) { array[index] = value; } intermediate (store, size); }
ここで、
関数intermediate
はstore
のアドレスを引数として受け取っています。
intermediate
がstore
を呼び出すと、
store
に渡された引数はarray
への値の格納に使われます。
しかし、
このテクニックは、
入れ子関数を包含している関数
(この例ではhack
)
が終了しない限りにおいてのみ有効です。
入れ子関数を包含する関数が終了した後に、 その入れ子関数のアドレスを使ってその入れ子関数を呼び出そうとすると、 とんでもない事態が発生することになるでしょう。 入れ子関数を包含するスコープが終了した後にその入れ子関数を呼び出してしまい、 その入れ子関数が既にスコープ内には存在しない変数を参照したとしても、 運良く問題に遭遇せずにすむこともあるかもしれませんが、 そのような危険を冒すのは賢明なことではありません。 しかし、 スコープ内には存在しなくなってしまったものを 入れ子関数がまったく参照していないのであれば、 何も問題は発生しないはずです。
GNU CCは、 入れ子関数のアドレスの獲得を、 トランポリン(trampoline)と呼ばれるテクニックを使って実装しています。 これに関する解説が、 `http://master.debian.org/~karlheg/Usenix88-lexic.pdf' にあります。
入れ子関数を包含する関数から継承されたラベルが、
その包含関数の中で明示的に宣言されているのであれば、
入れ子関数はそのラベルにジャンプすることができます
(局所的に宣言されたラベルを参照)。
そのようなジャンプは、
goto
を実行した入れ子関数だけでなく、
その入れ子関数が呼び出されるまでに途中で呼び出された関数をすべて終了させ、
即時に包含関数側に制御を戻します。
以下に例を示します。
bar (int *array, int offset, int size) { __label__ failure; int access (int *array, int index) { if (index > size) goto failure; return array[index + offset]; } int i; ... for (i = 0; i < size; i++) ... access (array, i) ... ... return 0; /*access
がエラーを検出すると、 制御はaccess
からここへ移る */ failure: return -1; }
入れ子関数は常に内部結合(internal linkage)を行います。
入れ子関数をextern
を指定して宣言するのは誤りです。
入れ子関数を定義する前に、
その入れ子関数を宣言する必要がある場合には、
auto
を使ってください
(これ以外の場合において、
関数宣言に対してauto
を使うのは無意味です)。
bar (int *array, int offset, int size) { __label__ failure; auto int access (int *, int); ... int access (int *array, int index) { if (index > size) goto failure; return array[index + offset]; } ... }
以下に説明する組み込み関数を使うことで、 ある関数の引数の数や型が分からなくても、 その関数が受け取った引数を記録して、 別の関数を同じ引数で呼び出すことができます。
また、 その関数が返そうとした戻り値のデータ型が分からなくても (そのデータ型が、 呼び出し側が期待しているものと同じである限り)、 その関数呼び出しの戻り値を記録して、 後にその値を返すことができます。
__builtin_apply_args ()
void *
型のポインタを返します。
これは、
カレントな関数に対して渡された引数と同一の引数を使って関数呼び出しを行う方法
を記述するデータへのポインタです。
この関数は、
引数ポインタ・レジスタ、
構造体値のアドレス、
および、
関数へ引数を渡すのに使われる可能性のあるすべてのレジスタの内容を、
スタック上に割り当てられたメモリ・ブロックに待避します。
その後、
そのブロックのアドレスを返します。
__builtin_apply (function, arguments, size)
void *
型)とsize(int
型)
によって表わされるパラメータのコピーを使って、
function(void (*)()
型)を呼び出します。
argumentsの値は、
__builtin_apply_args
によって返された値でなければなりません。
引数sizeは、
スタック上の引数データのサイズをバイト単位で指定します。
この関数は、
functionが返した値が何であれ、
その値を返す方法を記述するデータへのvoid *
型のポインタを返します。
そのデータは、
スタック上に割り当てられたメモリ・ブロックの中に待避されます。
sizeに指定するのに適切な値を計算することは、
常に簡単なことであるとは限りません。
この値は、
入力引数の領域からコピーされてスタックにプッシュされるべきデータの量を計算するために、
__builtin_apply
によって使われます。
__builtin_return (result)
__builtin_apply
から返された値を指定しなければなりません。
初期化子(initializer)とともにtypedef
宣言を使うことで、
式の型に名前を与えることができます。
以下に、
式expの型の名前としてnameを定義する方法を示します。
typedef name = exp;
これは、 式の中に複数の文を持たせる機能と併用すると役に立ちます。 以下に、 任意の算術型に対して作用する安全な「最大値を返す」マクロを定義するのに、 これら2つの機能を併用する方法を示します。
#define max(a,b) \ ({typedef _ta = (a), _tb = (b); \ _ta _a = (a); _tb _b = (b); \ _a > _b ? _a : _b; })
局所変数に対してアンダースコアで始まる名前を使う理由は、
a
やb
が置き換えられる式の中で使われている変数の名前との衝突を回避するためです。
最終的には、
初期化子の後ろからスコープが開始されるような変数を宣言することを可能にする、
新しい形式の宣言構文を設計したいと考えています。
こちらの方が、
名前の衝突を防ぐ手段としてはより信頼性のあるものになるでしょう。
typeof
による型への参照
式の型を参照する別の方法にtypeof
があります。
このキーワードを使うときの構文はsizeof
の構文に似ていますが、
意味論的には、
typedef
により定義された型名のような働きをします。
typeof
への引数を記述する方法は2つあります。
1つは式を使う方法で、
もう1つは型を使う方法です。
以下に、
式を使う方法の例を示します。
typeof (x[0](1))
ここでは、
x
が関数の配列であると仮定しています。
この例によって表現される型は、
関数の戻り値の型です。
次に、 型名を引数に使う例を示します。
typeof (int *)
この例では、
表現される型はint
へのポインタ型です。
ANSI Cのプログラムにインクルードされたときに
問題なく使えなければならないヘッダ・ファイルを書いている場合は、
typeof
の代わりに__typeof__
と書いてください。
代替キーワードを参照してください。
typeof
は、
typedef名が使えるところであればどこでも使うことができます。
例えば、
宣言、
キャスト、
sizeof
の中、
typeof
の中でそれを使うことができます。
x
が指し示すものの型としてy
を宣言します。
typeof (*x) y;
x
が指し示すものの型の配列としてy
を宣言します。
typeof (*x) y[4];
y
を宣言します。
typeof (typeof (char *)[4]) y;これは、 次のような古くからあるCの宣言と同等です。
char *y[4];
typeof
を使う宣言の意味と、
なぜそれが役に立つことがあるのかを理解するために、
これを次のようなマクロで書き換えてみましょう。
#define pointer(T) typeof(T *) #define array(T, N) typeof(T [N])すると、 上の宣言は次のように書き換えることができます。
array (pointer (char), 4) y;このように、
array (pointer (char), 4)
は、
char
への4つのポインタから構成される配列の型です。
複合式(compound expression)、 条件式、 および、 キャストは、 そのオペランドがlvalueである限り、 lvalueとして使うことができます。 このことは、 アドレスを取ったり、 値を格納したりすることができるということを意味しています。
標準C++では、 複合式と条件式をlvalueとして使うことができますし、 参照型に対するキャストを使うこともできます。 したがって、 この拡張機能をC++で書かれたソース・コードにおいて使用することには賛成できません。
複合式の中の最後の式がlvalueであれば、 複合式に対して値を代入することができます。 以下の2つの式は同等です。
(a, b) += 5 a, (b += 5)
同様に、 複合式のアドレスを取ることもできます。 以下の2つの式は同等です。
&(a, b) a, &b
条件式は、 その型がvoidではなく、 かつ、 真のときに分岐する部分と偽のときに分岐する部分がともに正当なlvalueであれば、 正当なlvalueです。 例えば、 以下の2つの式は同等です。
(a ? b : c) = 5 (a ? b = 5 : (c = 5))
キャストは、
そのオペランドがlvalueであれば、
正当なlvalueです。
左辺側にキャストが使われる単純代入は、
まず右辺側をキャストで指定された型に変換した後に、
左辺側のキャストが実行される前の式の型に変換することによって実現されます。
値が格納された後に、
代入の型に合うように、
その値はキャストで指定された型に再変換されます。
したがって、
a
の型がchar *
である場合、
次の2つの式は同等です。
(int)a = 5 (int)(a = (char *)(int)5)
`+='のような算術演算を伴う代入がキャストに対して適用された場合、 キャストにより変換される型を使って算術演算が実行されます。 その後は、 前のケースと同様です。 したがって、 以下の2つの式は同等です。
(int)a += 5 (int)(a = (char *)(int) ((int)a + 5))
lvalueに対するキャストのアドレスを取ることはできません。
なぜなら、
そのようなアドレスは整合性のある使い方ができないからです。
仮に、
f
の型がfloat
である場合に、
&(int)f
によってアドレスを取ることができるとしましょう。
すると以下の文は、
浮動小数点値が入るべき箇所に、
整数値のビット・パターンを格納しようとすることになります。
*&(int)f = 1;
これは、
(int)f = 1
の動作とはまったく異なります。
こちらは、
1を浮動小数点値に変換してから格納します。
このような不整合をもたらすよりも、
キャストに対する`&'の使用を禁止するほうが良いと考えました。
どうしてもf
のアドレスをint *
型のポインタにしたいのであれば、
単に(int *)&f
と書くことができます。
条件式の真ん中のオペランドは省略することができます。 この場合、 最初のオペランドの値が0以外であれば、 その値が条件式の値そのものになります。
したがって、 条件式
x ? : y
は、
x
の値が0以外であればx
の値を取り、
それ以外の場合はy
の値を取ります。
この例は、 以下とまったく同等です。
x ? x : y
このような単純なケースでは、 真ん中のオペランドを省略できても特に役には立ちません。 これが役に立つのは、 最初のオペランドが副作用を実際に持つ場合、 あるいは、 (それがマクロ引数であって) 副作用を持つ可能性がある場合です。 このような場合に、 最初のオペランドを真ん中に繰り返し記述すると、 副作用が2回働くことになります。 真ん中のオペランドを省略すれば、 同一のオペランドを再評価することによる望ましくない結果をもたらすことなく、 既に評価済みの値を再利用することになります。
GNU Cは、
int
の2倍の長さを持つ整数のためのデータ型をサポートしています。
有符号整数についてはlong long int
、
無符号整数についてはunsigned long long int
と書きます。
型がlong long int
の整数定数を作成するためには、
整数値の後ろに接尾語LL
を付けます。
型がunsigned long long int
の整数定数を作成するためには、
整数値の後ろに接尾語ULL
を付けます。
これらの型は、 他の任意の整数型と同じように算術演算で使用することができます。 これらの型に対する加算、 減算、 ビット単位のブール演算は、 すべての種類のマシン上においてオープン・コード化されます(open-coded)。 乗算は、 ワードの乗算結果をダブル・ワードにまで拡張できるような乗算命令をサポートするマシンであれば、 オープン・コード化されます。 除算とシフト演算は、 そのための特別なサポートを提供するマシン上でのみオープン・コード化されます。 オープン・コード化されない演算は、 GNU CCに付属している特別なライブラリ・ルーチンを使用します。
関数プロトタイプを宣言せずに、
関数の引数にlong long
型を使うと、
落し穴にはまる可能性があります。
ある関数がその引数にint
型を期待している場合にlong long int
型の値を渡すと、
呼び出し側とサブルーチン側とで引数のバイト数の解釈に食い違いが生じるために、
混乱が発生することになります。
関数がlong long int
を期待しているところにint
を渡す場合も、
同様の事態が発生します。
このような問題を回避するのに最良の方法はプロトタイプを使うことです。
GNU Cは複素数のデータ型をサポートしています。
複素整数型と複素浮動小数点型の両方を、
キーワード__complex__
を使って宣言することができます。
例えば、
`__complex__ double x;'は、
実数部と虚数部の両方がdouble
型である変数としてx
を宣言します。
`__complex__ short int y;'は、
実数部と虚数部がshort int
型である変数としてy
を宣言します。
このような例は役に立ちそうにはありませんが、
複素数型の集合がそろっていることを示しています。
複素数データ型の定数を記述するには、
接尾語`i'または`j'を使います
(両者は同等ですので、
どちらを使っても構いません)。
例えば、
2.5fi
の型は__complex__ float
であり、
3i
の型は__complex__ int
です。
このような定数は常に純粋な虚数値ですが、
実数定数に虚数定数を加算することにより、
任意の複素数値を作ることができます。
複素数値を持つ式expから実数部を抽出するには、
__real__ exp
と書きます。
同様に、
虚数部を抽出するには__imag__
を使います。
演算子`~'は、 複素数型の値に対して使われると、 その複素数の共役複素数を作ります。
GNU CCは、
複素数の自動変数を非連続的な形で割り当てることができます。
実数部をレジスタに置き、
虚数部をスタック上に置くこと
(あるいはその逆)
さえ可能です。
サポートされるデバッグ情報形式のどれも、
このような非連続的な割り当てを表現する方法を提供していません。
そのためGNU CCは、
非連続的な複素数変数を、
あたかもそれが2つの別々の非複素数変数であるかのように記述します。
変数の実際の名前がfoo
であるとすると、
2つの架空の変数にはfoo$real
とfoo$imag
という名前が与えられます。
デバッガを使って、
これら2つの架空の変数の値を調べたり、
値を設定したりすることができます。
将来のGDBバージョンでは、 こうした実数部と虚数部のペアを認識できるようになり、 それらを複素数型の単一の変数として扱うようになるでしょう。
GNU CCは、
1.55e1
のような通常の10進表記だけでなく、
0x1.fp3
のように16進形式で記述された浮動小数点数も認識します。
この形式では、
16進の先頭を表わす0x
と、
p
またはP
による指数フィールドは必須です。
指数部は、
有効数字に対して乗算される、
2の累乗を表わす10進数です。
したがって、
0x1.f
は1 15/16であり、
p3
によってそれが8倍されるので、
0x1.fp3
の値は1.55e1
に等しくなります。
10進表記による浮動小数点数とは異なり、
16進表記の場合には指数部が常に必要となります。
これがないと、
コンパイラは、
例えば0x1.f
の持つあいまいさを解決することができなくなります。
f
はfloat
型の浮動小数点定数の拡張子でもあるため、
0x1.f
は、
1.0f
と1.9375
のどちらの意味にもなりえます。
GNU Cでは長さが0の配列を使うことができます。 長さが0の配列は、 構造体の最後の要素として大変役に立ちます。 それは実際には、 可変長オブジェクトのヘッダとなります。
struct line { int length; char contents[0]; }; { struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length); thisline->length = this_length; }
標準 Cでは、
contents
の長さを1にしなければなりません。
このことは、
領域を無駄に消費するか、
もしくは、
malloc
への引数が複雑になるということを意味しています。
GNU Cでは可変長の自動配列を使うことができます。 可変長自動配列の宣言は他の任意の自動配列の宣言と似ていますが、 指定される長さが定数式ではないところが異なります。 記憶域は、 配列が宣言されたところで割り当てられ、 その宣言を包含する波括弧{}(brace-level)が終了したところで解放されます。 以下に例を示します。
FILE * concat_fopen (char *s1, char *s2, char *mode) { char str[strlen (s1) + strlen (s2) + 1]; strcpy (str, s1); strcat (str, s2); return fopen (str, mode); }
配列名のスコープからジャンプ等で抜け出ると、 その記憶域は解放されます。 スコープの外から中へジャンプして入り込むことはできません。 このようなことをするとエラー・メッセージが出力されます。
関数alloca
を使って、
可変長配列とほとんど同様の結果を実現することができます。
関数alloca
は、
(すべてのCの実装で利用可能なわけではありませんが)
他の多くのCの実装で利用可能です。
とはいうものの、
可変長配列の方が洗練されています。
これら2つの方法には、
ほかにも相違点があります。
alloca
によって割り当てられた領域は、
alloca
の呼び出しを包含する関数が終了するまで存在します。
可変長配列の領域は、
配列名のスコープが終了するとすぐに解放されます
(同一の関数の中で可変長配列とalloca
の両方を使うと、
可変長配列の領域が解放されるときに、
その可変長配列が割り当てられたあとにalloca
によって割り当てられた領域も
すべて解放されることになります)。
可変長配列は関数への引数としても使うことができます。
struct entry tester (int len, char data[len][len]) { ... }
配列の長さは、
記憶域が割り当てられるときに一度だけ計算されます。
その長さは、
sizeof
が使われる場合に備えて、
配列のスコープが終了するまで記憶されます。
パラメータにおいて配列を先に渡し、 そのあとで長さを渡したい場合には、 パラメータ・リストにおける前方宣言(forward declaration)を使うことができます。 これもまた、 GNUの拡張機能です。
struct entry tester (int len; char data[len][len], int len) { ... }
セミコロンの前の`int len'がパラメータ前方宣言です。
その役割は、
data
の宣言が解析されるときにlen
を既知の名前にすることです。
このようなパラメータの前方宣言は、 パラメータ・リストにおいて何個でも書くことができます。 個々の前方宣言はカンマまたはセミコロンにより区切ることができますが、 最後の前方宣言だけはセミコロンで終わらなければなりません。 この最後のセミコロンのあとに「真の」パラメータ宣言が続きます。 個々の前方宣言と「真の」宣言とは、 パラメータの名前とデータ型の点で一致していなければなりません。
GNU Cにおけるマクロは、 関数とほぼ同様に、 可変個数の引数を受け取ることができます。 マクロの定義構文は関数の定義に使われるものとほぼ同様です。 以下に例を示します。
#define eprintf(format, args...) \ fprintf (stderr, format , ## args)
ここではargs
が残余引数(rest argument)です。
それは、
マクロ呼び出しに含まれるのと同数の、
0個以上の引数を取ります。
マクロ呼び出しに含まれる引数すべてと個々の引数の間を区切るカンマとが、
args
の値になります。
マクロ本体の中でargs
が使われている部分は、
この値によって置き換えられます。
したがって、
以下のような展開が行われることになります。
eprintf ("%s:%d: ", input_file_name, line_number) ==> fprintf (stderr, "%s:%d: " , input_file_name, line_number)
文字列定数の後ろのカンマはeprintf
の定義から取られているのに対して、
最後のカンマはargs
の値から取られていることに注意してください。
`##'を使っているのは、
args
の部分に相当する引数が存在しないような状況に対処するためです。
このような場合、
args
の値は空であり、
定義の中の2番目のカンマが邪魔になります。
このカンマがマクロの展開後に残ると、
以下のような結果になります。
fprintf (stderr, "success!\n" , )
これは、 Cの構文としては不正です。 `##'を使うとカンマが除去されるので、 以下のような結果になります。
fprintf (stderr, "success!\n")
これは、 GNU Cプリプロセッサの特別な機能です。 空の残余引数の前の`##'は、 `##'の前にある空白類以外の文字の並びをマクロ定義から除去します (別のマクロ引数が前にある場合は、 それらの引数は除去されません)。
前にある空白類以外の文字の並びのうち最後のものを除去するよりも、 最後のプリプロセッサ・トークンを除去する方が望ましいかもしれません。 実際、 この機能をいつかそのように変更するかもしれません。 この機能の定義が変更されたあとでも実際上の意味が変わらないようにするために、 前にある空白類以外の文字の並びが単一のトークンであるように マクロ定義を記述することをお勧めします。
lvalueではない配列に対して添字を使うことができます。 ただし、 単項演算子`&'は使えません。 例えば、 以下の例はGNU Cでは正当ですが、 他のCでは正当ではありません。
struct foo {int a[4];}; struct foo f(); bar (int index) { return f().a[index]; }
void
型へのポインタと関数ポインタに対する算術演算
GNU Cでは、
void
型へのポインタと関数ポインタに対する加算と減算がサポートされています。
これは、
void
のサイズと関数のサイズを1として扱うことで実現されています。
その結果として、
void
型と関数型に対するsizeof
も使うことができ、
これらは値1を返します。
`-Wpointer-arith'オプションは、 これらの拡張機能が使われている場合に警告メッセージを出力するよう要求するものです。
標準C++と同様、 GNU Cでも、 自動変数に割り当てられた集合体の初期化子の要素は、 定数式である必要はありません。 以下に、 実行時に変化する要素を使った初期化子の例を示します。
foo (float f, float g) { float beat_freqs[2] = { f-g, f+g }; ... }
GNU Cは生成関数式(constructor expression)をサポートしています。 生成関数式の外見は、 初期化子を持つキャストのようなものです。 その値は、 キャストで指定された型のオブジェクトで、 初期化子で指定された要素を持ちます。
通常、
指定される型は構造体です。
struct foo
とstructure
が以下に示すように宣言されているものとしましょう。
struct foo {int a; char b[2];} structure;
以下に、
生成関数式によってstruct foo
を生成する例を示します。
structure = ((struct foo) {x + y, 'a', 0});
これは以下のように書くのと同等です。
{ struct foo temp = {x + y, 'a', 0}; structure = temp; }
配列を生成することもできます。 生成関数式のすべての要素が、 初期化子の中で使うのに適する単純な定数式である (あるいは、 単純な定数式から構成される) のであれば、 その生成関数式はlvalueであり、 以下に示すように、 その最初の要素へのポインタに強制的に型変換することができます。
char **foo = (char *[]) { "x", "y", "z" };
その要素が単純な定数ではないような配列生成関数式は、
あまり役に立ちません。
というのは、
そのような生成関数式はlvalueではないからです。
それを正当に使う方法は2つしかありません。
配列への添字付けを行う場合と配列変数の初期化を行う場合です。
前者は、
おそらくswitch
文よりも処理速度が遅くなるでしょう。
また後者は、
通常のCの初期化子が行うことと同一のことを行うだけです。
以下に、
配列生成関数式に添字付けを行う例を示します。
output = ((int[]) { 2, x, 28 }) [input];
スカラ(scalar)型と共用体型に対する生成関数式も使うことができますが、 この場合の生成関数式はキャストと同じことになります。
標準Cでは、 初期化子の要素の現れる順序は、 固定、 かつ、 初期化される配列や構造体の要素の順序と同一である必要があります。
GNU Cでは、 要素が適用される配列のインデックス、 または、 構造体のフィールド名を指定することによって、 任意の順序で要素を並べることができます。 この拡張機能はGNU C++では実装されていません。
配列のインデックスを指定するには、 要素の値の前に`[index]'または`[index] ='と書きます。 以下に例を示します。
int a[6] = { [4] 29, [2] = 15 };
これは以下と同等です。
int a[6] = { 0, 0, 15, 0, 29, 0 };
初期化される配列が自動変数であっても、 インデックスの値は定数式でなければなりません。
ある範囲の要素を同一の値で初期化するには、 `[first ... last] = value' のように書きます。 以下に例を示します。
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
インデックスとして指定された値のうち最大のものに1を加えた値が 配列の長さになることに注意してください。
構造体の初期化子の場合は、 要素の値の前に`fieldname:'と書くことによって、 初期化するフィールドの名前を指定します。 例えば、 以下のような構造体が与えられているとしましょう。
struct point { int x, y; };
このとき、
struct point p = { y: yvalue, x: xvalue };
という初期化は、 以下と同等です。
struct point p = { xvalue, yvalue };
同一の意味を持つ別の構文として、 `.fieldname ='があります。 以下に例を示します。
struct point p = { .y = yvalue, .x = xvalue };
共用体を初期化するときに、 初期化の対象となる共用体の要素を指定するのに、 要素ラベルを使うことができます (コロンを使う構文とピリオドと等号を使う構文のどちらでも使えます)。 例えば、
union foo { int i; double d; }; union foo f = { d: 4 };
は、
4をdouble
型に変換して、
共用体の2番目の要素を使って共用体の中に格納します。
これとは対照的に、
4をキャストしてunion foo
型に変換すると、
4は整数なので、
共用体の中の整数i
として格納されることになります
(共用体型へのキャストを参照してください)。
要素に名前をつけるテクニックを、 通常のCにおける連続した要素の初期化と組み合わせることができます。 初期化子の要素のうちラベルを持たないものは、 配列や構造体の連続した要素の中の次の要素に適用されます。 例えば、
int a[6] = { [1] = v1, v2, [4] = v4 };
は、 以下と同等です。
int a[6] = { 0, v1, v2, 0, v4, 0 };
配列の初期化子の要素にラベルをつけるのは、 配列のインデックスが文字や列挙型の値である場合に特に役に立ちます。 以下に例を示します。
int whitespace[256] = { [' '] = 1, ['\t'] = 1, ['\h'] = 1, ['\f'] = 1, ['\n'] = 1, ['\r'] = 1 };
単一のcase
ラベルの中で、
ある連続した値の範囲を指定することができます。
以下に例を示します。
case low ... high:
これは、
low以上high以下の個々の整数値について、
個別にcase
ラベルを記述するのと同等の効果を持ちます。
この機能は、 ASCII文字コードの範囲を指定するのに特に役に立ちます。
case 'A' ... 'Z':
注意: ...
の前後には空白を書いてください。
そうしないと、
整数値を使う場合に正しく解析されない可能性があります。
例えば、
case 1 ... 5:
のように書いてください。
case 1...5:
のようには書かないでください。
共用体型へのキャストは、
指定される型が共用体であるという点を除けば、
他のキャストと似ています。
型は、
union tag
によって指定することもできますし、
あるいは、
typedefされた名前によって指定することもできます。
ただし、
共用体へのキャストは、
実際にはキャストではなく生成関数です。
したがって、
通常のキャストのようにlvalueになることはありません
(生成関数式を参照してください)。
ある共用体型へキャストすることができる型は、 その共用体のメンバの型です。 よって、 以下のような共用体と変数が与えられた場合、
union foo { int i; double d; }; int x; double y;
x
とy
は両方ともunion
foo型へキャストすることができます。
共用体型の変数に対する代入式の右辺側においてキャストを使うことは、 その共用体のメンバに値を格納するのと同等です。
union foo u; ... u = (union foo) x == u.i = x u = (union foo) y == u.d = y
共用体へのキャストを関数への引数として使うこともできます。
void hack (union foo); ... hack ((union foo) x);
GNU Cでは、 プログラムの中で呼び出される関数に関して特定の情報を宣言することによって、 コンパイラによる関数呼び出しの最適化やソース・コードの綿密なチェックを 支援することができるようになります。
キーワード__attribute__
によって、
宣言をする際に特別な属性を指定することができます。
このキーワードの後ろに、
二重の丸括弧(())に囲まれた属性指定が続きます。
現在、
9個の属性noreturn
、
const
、
format
、
no_instrument_function
、
section
、
constructor
、
destructor
、
unused
、
weak
が関数に対して定義されています。
section
を含むその他の属性が、
変数宣言
(変数属性の指定を参照)
と型
(型属性の指定を参照)
に対してサポートされています。
個々のキーワードの前後に`__'を付けて属性を指定することもできます。
これにより、
同じ名前を持つマクロが定義済みであるような事態を心配することなく、
ヘッダ・ファイルの中でキーワードを使うことができるようになります。
例えば、
noreturn
の代わりに__noreturn__
を使うことができます。
noreturn
abort
やexit
のように、
復帰(return)することができないものが少しあります。
何もしなくても、
GNU CCはこれらの関数が復帰しないということを知っています。
プログラムによっては、
決して復帰しない独自の関数を定義するものがあります。
このような関数をnoreturn
と宣言することによって、
その関数が復帰しないということをコンパイラに知らせることができます。
以下に例を示します。
void fatal () __attribute__ ((noreturn)); void fatal (...) { ... /* エラー・メッセージを表示する */ ... exit (1); }
noreturn
キーワードは、
fatal
が復帰することがないものと想定するようコンパイラに指示します。
コンパイラは、
fatal
が万一復帰した場合に発生する事態のことはかまわずに、
最適化を行うことができます。
これにより、
少し良いバイナリ・コードが生成されます。
より重要なことは、
初期化されていない変数についての見せかけだけで意味のない
警告メッセージを回避するのに役立つということです。
呼び出し側の関数によって待避されるレジスタが、
noreturn
属性を持つ関数を呼び出す前に復元されると想定してはなりません。
noreturn
属性を持つ関数がvoid
以外の型の戻り値を持つのは無意味なことです。
noreturn
属性は、
バージョン2.5より前のバージョンのGNU Cでは実装されていません。
ある関数が復帰しないということを宣言する方法で、
カレント・バージョンといくつかの古いバージョンにおいて使えるものに、
以下のような方法があります。
typedef void voidfn (); volatile voidfn fatal;
const
const
属性を指定して宣言するべきです。
例えば、
int square (int) __attribute__ ((const));は、 この
square
という名前の仮の関数が、
プログラムの中で指定された回数よりも少ない回数しか呼び出さなくても
安全であるということを表わしています。
const
属性は、
バージョン2.5より前のバージョンのGNU Cでは実装されていません。
ある関数が副作用を持たないということを宣言する方法で、
カレント・バージョンといくつかの古いバージョンにおいて使えるものに、
以下のような方法があります。
typedef int intfn (); extern const intfn square;この方法は、 2.6.0以降のGNU C++では使えません。 C++の言語仕様では、 `const'は戻り値に対して指定しなければならないとなっているからです。 引数としてポインタを取り、 そのポインタが指し示すデータを参照する関数には、
const
属性を宣言してはなりません。
同様に、
const
属性を持たない関数を呼び出す関数には、
通常はconst
属性を宣言してはなりません。
const
属性を持つ関数がvoid
型の戻り値を返すのは無意味なことです。
format (archetype, string-index, first-to-check)
format
属性は、
その関数が、
書式文字列に照らし合わせて型チェックを行うべき引数、
すなわち、
printf
、
scanf
、
strftime
のような方式の引数を取ることを指定します。
例えば、
extern int my_printf (void *my_object, const char *my_format, ...) __attribute__ ((format (printf, 2, 3)));という宣言を行うと、 コンパイラは、
printf
方式の書式文字列引数であるmy_format
を使って、
my_printf
の呼び出しにおける引数の整合性をチェックします。
archetypeパラメータは、
書式文字列がどのように解釈されるかを決定するもので、
printf
、
scanf
、
strftime
のいずれかでなければなりません。
string-indexパラメータは、
どの引数が書式文字列引数であるかを
(1から始まる数で)
指定します。
一方、
first-to-checkは、
書式文字列に照らし合わせてチェックするべき最初の引数の番号です。
(vprintf
のように)
チェックされるべき引数を指定できない関数に対しては、
第3パラメータに0を指定してください。
この場合、
コンパイラは、
書式文字列だけを対象にして整合性のチェックを行います。
上の例では、
書式文字列
(my_format
)
は関数my_printf
の第2引数であり、
チェックするべき引数は第3引数から始まっています。
したがって、
format
属性の正しいパラメータは2と3です。
書式文字列を引数として取るユーザ独自の関数を、
format
属性を使って指定することができます。
これによってGNU CCは、
その関数呼び出しにおける誤りをチェックできるようになります。
ANSIライブラリ関数のprintf
、
fprintf
、
sprintf
、
scanf
、
fscanf
、
sscanf
、
strftime
、
vprintf
、
vfprintf
、
vsprintf
については、
(`-Wformat'を使って)
そのような警告が要求されているときにはいつでも、
コンパイラは書式のチェックを行います。
したがって、
ヘッダ・ファイル`stdio.h'を修正する必要はありません。
format_arg (string-index)
format_arg
属性は、
その関数がprintf
やscanf
のような方式の引数を取り、
それを修正
(例えば、他の言語に翻訳)
した後に、
printf
やscanf
のような関数に渡すということを指定します。
例えば、
extern char * my_dgettext (char *my_domain, const char *my_format) __attribute__ ((format_arg (2)));という宣言を行うと、 コンパイラは、
my_dgettext
の呼び出しにおける引数を、
printf
方式の書式文字列引数であるmy_format
に照らし合わせ、
整合性のチェックを行います。
このmy_dgettext
の呼び出しの結果は、
printf
、
scanf
、
strftime
のような方式の関数に渡されます。
string-indexパラメータは、
どの引数が書式文字列引数であるかを
(1から始まる数で)
指定します。
書式文字列を変更するようなユーザ独自の関数がある場合に、
format-arg
属性を使って、
そのような関数であるということを指定することができます。
これにより、
関数printf
、
scanf
、
strftime
のオペランドが、
そのような独自関数への呼び出しになっている場合に、
これをチェックすることができます。
コンパイラは常に、
gettext
、
dgettext
、
dcgettext
をこのように取り扱います。
no_instrument_function
section ("section-name")
text
セクションに置きます。
しかしときには、
別のセクションを追加したり、
特定の関数を特別なセクションに置くことが必要になることがあります。
section
属性は、
関数が特定のセクション内に置かれるよう指定します。
例えば、
extern void foobar (void) __attribute__ ((section ("bar")));という宣言は、 関数
foobar
をbar
セクションに置きます。
ファイル形式によっては、
セクションの任意指定がサポートされていないものもあります。
したがってsection
属性は、
すべてのプラットフォームで利用可能なわけではありません。
あるモジュールのすべての内容を特定のセクションにマップする必要がある場合には、
この属性ではなく、
リンカの機能を使うことを検討してください。
constructor
destructor
constructor
属性を指定された関数は、
main ()
関数が実行される前に、
自動的に呼び出されるようになります。
同様に、
destructor
属性を指定された関数は、
main ()
の実行が完了した後、
または、
exit ()
が呼び出された後に、
自動的に呼び出されるようになります。
これらの属性を持つ関数は、
プログラムが実行される間に暗黙のうちに使われるデータを初期化するのに役に立ちます。
これらの属性は、
現在のところObjective Cでは実装されていません。
unused
weak
weak
属性を指定された宣言は、
広域(global)シンボルではなく、
弱い(weak)シンボルとして出力されるようになります。
これは主として、
ユーザのソース・コードの中で無効にすることのできる
ライブラリ関数を定義するのに役に立ちますが、
関数以外の宣言においても使うこともできます。
弱いシンボルはELFターゲットにおいてサポートされています。
また、
GNUアセンブラとGNUリンカを使っている場合は、
a.outターゲットにおいてもサポートされています。
alias ("target")
alias
属性を指定された宣言は、
別のシンボルへの別名として出力されます。
その別のシンボルは指定されなければなりません。
例えば、
void __f () { /* 何かを行う */; } void f () __attribute__ ((weak, alias ("__f")));は、 `f'を`__f'への弱い(weak)別名として宣言します。 C++では、 その別名が指し示すシンボルは、 マングルされた(mangled)名前で指定されなければなりません。 すべてのターゲット・マシンにおいてこの属性がサポートされているわけではありません。
no_check_memory_usage
asm
文の使用は、
コンパイラが正しく処理することができないので、
許されません。
関数にこの属性を指定すると、
その関数については、
メモリ・チェックを行うコードは無効化されます。
これにより、
異なるオプションを使って別個にコンパイルすることなく
asm
文を使うことができるようになります。
また、
もしそうしたいのであれば、
ユーザ独自のサポート・ルーチンを書くこともできます。
このオプションを指定してコンパイルしても、
それが無限再帰に陥ることはありません。
regparm (number)
regparm
属性により、
コンパイラは最高でnumberで指定される個数までの整数引数を、
スタックではなくEAX、
EDX、
ECXレジスタに入れて渡すようになります。
可変個数の引数を取る関数の場合は、
この属性を指定されても、
スタック上においてすべての引数を渡されます。
stdcall
stdcall
属性により、
関数が可変個数の引数を取るのでない限り、
引数を渡すのに使われたスタック領域は呼び出された関数がポップするものと、
コンパイラは想定するようになります。
Windows NT用のPowerPCコンパイラは、
現在のところstdcall
属性を無視します。
cdecl
cdecl
属性により、
引数を渡すのに使われたスタック領域は呼び出した関数がポップするものと、
コンパイラは想定するようになります。
これは、
`-mrtd'オプションの効果を無効にするのに役に立ちます。
Windows NT用のPowerPCコンパイラは、
現在のところcdecl
属性を無視します。
longcall
longcall
属性により、
コンパイラは常にポインタを使ってその関数を呼び出すようになります。
これにより、
カレントな位置から64メガバイト
(67,108,864バイト)
を超えて離れた位置にある関数でも呼び出すことができます。
dllimport
dllimport
属性により、
コンパイラは、
Windows NTのDLLによりセットアップされた関数ポインタを指し示す広域ポインタを使って
その関数を呼び出すようになります。
ポインタの名前は、
__imp_
と関数名を結合することにより作られます。
dllexport
dllexport
属性により、
コンパイラは、
その関数をdllimport
属性を使って呼び出すことができるように、
関数ポインタへの広域ポインタを提供するようになります。
ポインタの名前は、
__imp_
と関数名を結合することにより作られます。
exception (except-func [, except-arg])
exception
属性により、
コンパイラは、
宣言された関数のために出力する構造化された例外テーブルのエントリを変更するようになります。
except-funcに指定される文字列または識別子が、
構造化された例外テーブルの第3のエントリに入れられます。
それは、
例外が発生したときに例外処理メカニズムにより呼び出される関数を表わします。
except-argに文字列または識別子が指定されると、
それは構造化された例外テーブルの第4のエントリに入れられます。
function_vector
interrupt_handler
eightbit_data
tiny_data
interrupt
model (model-name)
small
、
medium
、
large
のいずれかであり、
コード・モデルの1つを表わします。
スモール・モデルのオブジェクトは
(そのアドレスがld24
命令によってロードできるように)
メモリの低位16MBの範囲内にあり、
bl
命令によって呼び出すことができます。
ミディアム・モデルのオブジェクトは、
32ビット・アドレス空間の任意の位置にあり
(そのアドレスをロードするために、
コンパイラはseth/add3
命令を生成します)、
bl
命令によって呼び出すことができます。
ラージ・モデルのオブジェクトは、
32ビット・アドレス空間の任意の位置にあり
(そのアドレスをロードするために、
コンパイラはseth/add3
命令を生成します)、
かつ、
bl
命令では到達不可能なことがあります
(その場合、
コンパイラは、
はるかに遅いseth/add3/jl
命令シーケンスを生成します)。
二重の丸括弧(())の内部において属性をカンマで区切るか、 属性宣言の直後に別の属性宣言を記述することによって、 宣言の中において複数の属性を指定することができます。
ANSI Cの#pragma
を使うべきであるとして、
__attribute__
機能に反対する人々がいます。
ANSI Cの#pragma
を使わない理由は2つあります。
#pragma
コマンドを生成することは不可能です。
#pragma
が異なるコンパイラにおいて持つ意味について
確定的なことを言うことができません。
これら2つの理由は、
#pragma
を使うよう提案されるであろうどのような場合においても、
適用できます。
どのような場合においても、
#pragma
を使うのは基本的に誤りです。
GNU Cは、 関数プロトタイプによって、 それよりも後ろにある、 プロトタイプを使わない古い方式の定義を無効にすることができるように、 ANSI Cを拡張しています。 以下の例を考えてみてください。
/* コンパイラが旧式のものでなければプロトタイプを使う */ #ifdef __STDC__ #define P(x) x #else #define P(x) () #endif /* プロトタイプ関数宣言 */ int isroot P((uid_t)); /* 古い方式の関数定義 */ int isroot (x) /* ??? lossage here ??? */ uid_t x; { return x == 0; }
型uid_t
がたまたまshort
であるとしましょう。
ANSI Cではこの例は許されません。
というのは、
プロトタイプを使わない古い方式の定義におけるサブワード(subword)引数は
汎整数拡張されるからです。
そのため、
この例においては、
関数定義の引数は実際にはint
であり、
プロトタイプにおける引数の型であるshort
と一致しません。
このANSI Cの制限により、
古くからあるCコンパイラとの間で移植性のあるソース・コードを記述するのが困難になります。
というのは、
プログラマには、
uid_t
の型が
short
、
int
、
long
のいずれであるかが分からないからです。
そこでGNU Cでは、
このような場合に、
プロトタイプがそれより後ろにある古い方式の定義を無効にすることができるようにしています。
より正確に言うと、
GNU Cでは、
関数プロトタイプにおける引数の型と、
それよりも後ろにある古い方式の定義によって指定される引数の汎整数拡張される前の型が
同一である場合、
前者が後者の汎整数拡張後の型を無効にします。
よってGNU Cにおいては、
上記の例は以下と同等となります。
int isroot (uid_t); int isroot (uid_t x) { return x == 0; }
GNU C++は古い方式の関数定義をサポートしていないので、 この拡張機能は無関係です。
GNU Cでは、
`//'により始まり行末まで続く、
C++方式のコメントを使うことができます。
他の多くのCの実装でもこのようなコメントを使うことができます。
これは、
将来のCの標準仕様に含まれそうです。
しかし、
`-ansi'または`-traditional'を指定した場合には、
C++方式のコメントは認識されません。
これは、
C++方式のコメントが、
dividend//*comment*/divisor
のような古くから使われている書き方と
両立しないからです。
GNU Cでは通常、 識別子の名前の中でドル記号を使うことができます。 これは、 古くからあるCの多くの実装においても、 そのような識別子を使うことができるからです。 しかし、 識別子の中のドル記号がサポートされないターゲット・マシンも少しあります。 その典型的な理由は、 ターゲット・アセンブラが識別子の中のドル記号を許さないということです。
ASCII文字ESCを表わすために、 文字列または文字定数の中において、 `\e'という文字の並びを使うことができます。
キーワード__alignof__
によって、
あるオブジェクトがどのように境界整列されるか、
あるいは、
ある型が通常必要とする最小の境界整列の値は何かを調べることができます。
その構文は、
sizeof
と同様です。
例えば、
あるターゲット・マシン上では、
double
型の値は8バイト境界に境界整列されなければならないとしましょう。
すると、
__alignof__ (double)
は8になります。
多くのRISCマシンでは実際こうなります。
もっと古くからあるマシン・デザインにおいては、
__alignof__ (double)
は4ですが、
2の場合もあります。
マシンによっては、
実際にまったく境界整列を必要としないものもあります。
そのようなマシンでは、
奇数アドレスにおいて任意のデータ型を参照することさえ許されます。
このようなマシンでは、
__alignof__
は型の推奨境界整列を報告します。
__alignof__
のオペランドが型ではなくlvalueの場合は、
__alignof__
の値は、
そのlvalueが取ると分かっている境界整列の値のうち最大の値となります。
この境界整列の値は、
そのlvalueのデータ型から決められることもありますし、
そのlvalueが構造体の一部である場合には、
その構造体から境界整列の値を継承することもあります。
例えば、
struct foo { int x; char y; } foo1;
という宣言のあとでは、
foo1.y
のデータ型自体は境界整列を一切必要としないにもかからわず、
__alignof__ (foo1.y)
の値は、
おそらく__alignof__ (int)
と同じ値の2か4になります。
オブジェクトの境界整列を指定する方法を提供してくれる関連機能に
__attribute__ ((aligned (alignment)))
;があります。
これについては、
次のセクションを参照してください。
キーワード__attribute__
により、
変数または構造体フィールドの特別な属性を指定することができます。
このキーワードの後ろに、
二重の丸括弧(())に囲まれた属性指定が続きます。
現在、
8個の属性
aligned
、
mode
、
nocommon
、
packed
、
section
、
transparent_union
、
unused
、
weak
が変数に対して定義されています。
その他の属性が、
関数(関数属性の宣言)および型(see 型属性の指定を参照)
に対して定義されています。
個々のキーワードの前後に`__'を付けて属性を指定することもできます。
これにより、
同じ名前を持つマクロが定義済みであるような事態を心配することなく、
ヘッダ・ファイルの中でキーワードを使うことができるようになります。
例えば、
aligned
の代わりに__aligned__
を使うことができます。
aligned (alignment)
int x __attribute__ ((aligned (16))) = 0;という宣言があると、 コンパイラは広域変数
x
を16バイト境界に割り当てます。
68040において、
16バイト境界に整列されたオペランドを必要とするmove16
命令にアクセスするのに、
これをasm
式と一緒に使うことができます。
構造体フィールドの境界整列を指定することもできます。
例えば、
ダブル・ワード境界に整列されたint
のペアを作成するには、
以下のように書くことができます。
struct foo { int x[2] __attribute__ ((aligned (8))); };これは、 共用体を作成して、 その共用体を強制的にダブル・ワード境界に整列させる
double
型のメンバを持たせるという方法の代わりになります。
関数の境界整列を指定することはできません。
関数の境界整列は、
マシンの要件によって決定されるものであって、
それを変更することはできません。
typedef名は、
単なる別名であって別個の型ではないので、
その境界整列を指定することはできません。
前の例に示されるように、
ある与えられた変数または構造体フィールドに対して、
コンパイラに使ってもらいたい境界整列を
(バイト単位で)
明示的に指定することができます。
あるいは、
境界整列の係数(factor)を省略することによって、
コンパイルのターゲット・マシンにとって有意味な最大の境界整列に
変数またはフィールドを境界整列するよう、
コンパイラに要求することもできます。
例えば、
以下のように書くことができます。
short array[3] __attribute__ ((aligned));
aligned
属性の指定において境界整列の係数を省略した場合にはいつでも、
宣言された変数またはフィールドの境界整列の値として、
コンパイルのターゲット・マシン上において
任意のデータ型に対して使用される境界整列の最大値を、
コンパイラが自動的に設定します。
このようにするとしばしば、
コピー操作がより効率的になります。
というのは、
このようにして境界整列された変数またはフィールドとの間でコピー処理を実行する場合に、
一度にコピーできるメモリ上のデータ量が最大の命令を
コンパイラが選択して使うことができるからです。
aligned
属性は、
境界整列の値を大きくすることしかできません。
しかし、
packed
属性を指定することによって、
その値を小さくすることも可能です。
これについては、
後に説明します。
リンカに固有の制限により、
aligned
属性の効果が制限されるかもしれないことに注意してください。
多くのシステムにおいて、
リンカは、
ある特定の境界整列の値を上限として、
その範囲内においてしか変数の境界整列を行うことができません
(リンカによっては、
サポートされる境界整列の最大値が非常に小さいものもあります)。
使っているリンカが、
最大でも8バイト境界までしか変数の境界整列を行えないのであれば、
__attribute__
においてaligned(16)
を指定しても、
実際には8バイト境界までしか実現できません。
より詳しい情報については、
リンカのドキュメントを参照してください。
mode (mode)
nocommon
nocommon
属性を指定すると、
その変数は0により初期化されます。
変数は、
1つのソース・ファイルの中でのみ初期化することができます。
packed
packed
属性は、
aligned
属性によってより大きな値が指定されていない限り、
変数または構造体フィールドが、
可能な境界整列の値のうち最小の値を取るべきであることを指定します。
この最小値は、
変数については1バイトであり、
フィールドについては1ビットです。
以下に示す構造体では、
フィールドx
にはpacked
属性が指定されているため、
a
の直後に置かれます。
struct foo { char a; int x[2] __attribute__ ((packed)); };
section ("section-name")
data
やbss
といったセクションに置きます。
しかしときには、
例えば特殊なハードウェアにマップするために、
追加のセクションが必要になったり、
特定の変数を特殊なセクションに置くことが必要になったりします。
section
属性は、
ある変数
(または関数)
が、
ある特定のセクション内に存在するよう指定します。
例えば、
以下の小さなプログラムでは、
いくつかの特別なセクション名を使っています。
struct duart a __attribute__ ((section ("DUART_A"))) = { 0 }; struct duart b __attribute__ ((section ("DUART_B"))) = { 0 }; char stack[10000] __attribute__ ((section ("STACK"))) = { 0 }; int init_data __attribute__ ((section ("INITDATA"))) = 0; main() { /* スタック・ポインタを初期化する */ init_sp (stack + sizeof (stack)); /* 初期データを初期化する */ memcpy (&init_data, &data, &edata - &data); /* シリアル・ポートを始動する */ init_duart (&a); init_duart (&b); }この例に示されているように、 広域変数の初期化定義のところで
section
属性を使います。
初期化の行われない変数宣言のところでsection
属性を使うと、
GNU CCは警告メッセージを出力するか、
そうでなければ、
この属性を無視します。
リンカの動作の仕組みが原因で、
完全に初期化が行われる広域定義においてしかsection
属性を使うことはできません。
リンカは、
個々のオブジェクトが一度だけ定義されることを要求しますが、
例外として、
初期化されない変数は仮にcommon
(またはbss
)
セクションに配置され、
複数回「定義」されることができます。
`-fno-common'フラグまたはnocommon
属性を使うことで、
強制的に変数を初期化させることができます。
ファイル形式によっては、
セクションの任意指定をサポートしていないものもありますので、
section
属性はすべてのプラットフォーム上で利用可能なわけではありません。
あるモジュールのすべての内容を特定のセクションにマップする必要がある場合には、
この属性ではなく、
リンカの機能を使うことを検討してください。
transparent_union
typedef
にこの属性を使うこともできます。
この場合、
その型を持つすべての関数パラメータに対して、
この属性が適用されます。
unused
weak
weak
属性については、
関数属性の宣言を参照してくださいにおいて説明されています。
model (model-name)
small
、
medium
、
large
のいずれかであり、
それぞれのコード・モデルを表わします。
スモール・モデルのオブジェクトは
(そのアドレスがld24
命令によってロードできるよう)
メモリの低位16MBの範囲内に存在します。
ミディアム・モデルのオブジェクトとラージ・モデルのオブジェクトは、
32ビット・アドレス空間の任意の位置に存在することができます
(そのアドレスをロードするために、
コンパイラはseth/add3
命令を生成します)。
複数の属性を指定するには、 例えば`__attribute__ ((aligned (16), packed))'のように、 二重の丸括弧(())の中で属性をカンマで区切ります。
キーワード__attribute__
により、
struct
型とunion
型を定義する際に、
それらの型に特殊な属性を指定することができます。
このキーワードの後ろに、
二重の丸括弧(())に囲まれた属性指定が続きます。
現在、
3個の属性
aligned
、
packed
、
transparent_union
が型に対して定義されています。
その他の属性が、
関数
(関数属性の宣言を参照)
と変数
(変数属性の指定を参照)
に対して定義されています。
これらの属性はいずれも、
キーワードの前後に`__'を付けて指定することもできます。
これにより、
同じ名前を持つマクロが定義済みであるような事態を心配することなく、
ヘッダ・ファイルの中でこれらの属性を使うことができるようになります。
例えば、
aligned
の代わりに__aligned__
を使うことができます。
aligned
属性とtransparent_union
属性は、
typedef
宣言の中において、
あるいは、
完結した列挙型、
構造体型、
共用体型の定義の終端の波括弧}の直後において指定することができます。
また、
packed
属性は、
定義の終端の波括弧}の後ろにおいてのみ指定することができます。
また、 終端の波括弧}の後ろではなく、 列挙タグ、 構造体タグ、 共用体タグと型の名前の間において、 属性を指定することもできます。
aligned (alignment)
struct S { short f[3]; } __attribute__ ((aligned (8))); typedef int more_aligned_int __attribute__ ((aligned (8)));という宣言によりコンパイラは、 (それが可能である限り)
struct S
またはmore_aligned_int
という型を持つ変数を、
少なくとも8バイト境界に割り当てて境界整列するよう保証することを強制されます。
Sparc上では、
すべてのstruct S
型の変数を8バイト境界に境界整列させると、
複数のstruct S
型の変数の間で値をコピーする際に、
コンパイラはldd
命令とstd
命令
(ダブル・ワードのロードとストア)
を使うことができるので、
実行時の効率が向上します。
ANSI C標準では、
ある与えられたstruct
型またはunion
型の境界整列の値は、
少なくとも、
そのstruct
、
union
のすべてのメンバの境界整列の値の
最小公倍数の完全倍数になっていなければならないという点に注意してください。
このことは、
struct
またはunion
の任意の1つのメンバに
aligned
属性を指定することによって、
struct
、
union
の境界整列を効果的に調整することが可能である
ということを意味しています。
しかし、
上の例において示される表記法は、
struct
型全体またはunion
型全体の境界整列を調整するよう
コンパイラに要求する方法としは、
より明白であり、
より直観的であり、
かつ、
より読みやすいものです。
前の例に示されるように、
ある与えられたstruct
型またはunion
型に対して
コンパイラに使ってもらいたい境界整列の値を
(バイト単位で)
明示的に指定することができます。
あるいは、
境界整列の係数(factor)を省略して、
コンパイルのターゲット・マシンにおいて有意味な最大の境界整列の値によって、
ある型を境界整列するようコンパイラに要求することもできます。
例えば、
以下のように書くことができます。
struct S { short f[3]; } __attribute__ ((aligned));
aligned
属性の指定において境界整列の係数を省略した場合、
コンパイラは常に、
コンパイルのターゲット・マシン上において
任意のデータ型に対して使用される境界整列の最大値を、
その型の境界整列の値として自動的に設定します。
このようにするとしばしば、
コピー操作がより効率的になります。
というのは、
このようにして境界整列された型を持つ変数との間でコピー処理を実行する場合に、
一度にコピーできるメモリ上のデータ量が最大の命令を
コンパイラが選択して使うことができるからです。
前の例では、
個々のshort
のサイズが2バイトであれば、
struct S
型全体のサイズは6バイトです。
この全体のサイズ以上の値を持つ2の累乗のうち最小の値は8です。
したがってコンパイラは、
struct S
型全体の境界整列を8バイトに設定します。
ある与えられた型に対して時間的に効率的な境界整列をコンパイラに選択させて、
その型のオブジェクトをそのまま個々に宣言するだけにすることもできますが、
時間的に効率的な境界整列を選択するというコンパイラの能力が主に役に立つのは、
その
(効率的に境界整列された)
型を持つ変数の配列を作成しようとしている場合のみであるということに注意してください。
効率的に境界整列された型を持つ変数の配列を宣言または使用するプログラムでは、
その型へのポインタに対するポインタ算術
(または、
それと結果的に同様のことになる添字付け)
も行っている可能性があります。
こうしたポインタ算術演算に対してコンパイラが生成するコードは、
効率的に境界整列された型の場合のほうが、
そうではない型の場合よりもずっと効率的であることが多いでしょう。
aligned
属性は、
境界整列の値を大きくすることしかできません。
しかし、
packed
属性を指定することによって、
その値を小さくすることも可能です。
これについては、
後に説明します。
リンカに固有の制限により、
aligned
属性の効果が制限されるかもしれないことに注意してください。
多くのシステムにおいて、
リンカは、
ある特定の境界整列の値を上限として、
その範囲内においてしか変数の境界整列を行うことができません
(リンカによっては、
サポートされる境界整列の最大値が非常に小さいものもあります)。
使っているリンカが、
最大でも8バイト境界までしか変数の境界整列を行えないのであれば、
__attribute__
においてaligned(16)
を指定しても、
実際には8バイト境界までしか実現できません。
より詳しい情報については、
リンカのドキュメントを参照してください。
packed
enum
、
struct
、
union
の型定義に指定されるこの属性は、
その型を表わすのに最小限必要とされるメモリだけを使うよう指定します。
struct
型とunion
型に対してこの属性を指定するのは、
構造体または共用体の個々のメンバにpacked
属性を指定するのと同等です。
また、
コマンドライン上において`-fshort-enums'フラグを指定するのは、
すべてのenum
定義においてpacked
属性を指定するのと同等です。
この属性は、
enum
定義の終端の波括弧}の後ろでのみ指定できます。
typedef
宣言の中では、
その宣言がenum
の定義を含んでいるのでない限り、
この属性を指定することはできません。
transparent_union
union
の型定義に指定されるこの属性は、
その共用体の型を持つ関数パラメータがあると、
その関数の呼び出しが特殊な方法で扱われるようになることを示します。
第1に、
transparent_union
属性が指定された共用体型に対応する引数は、
その共用体の任意のメンバの型を持つことができ、
それらの型へのキャストは一切必要ありません。
また、
その共用体のメンバにポインタ型のものがあれば、
対応する引数にはヌル・ポインタ定数またはvoidポインタ式を使うことができます。
さらに、
その共用体のメンバにvoidポインタ型のものがあれば、
対応する引数には任意のポインタ式を使うことができます。
共用体メンバの型がポインタであれば、
そのポインタによって参照される型に対するconst
のような修飾子は、
通常のポインタ変換の場合と同様、
尊重されなければなりません。
第2に、
関数に対してその引数が渡される際には、
transparent_union
属性が指定された共用体自体の呼び出し規約ではなく、
その共用体の1番目のメンバの呼び出し規約が使われます。
その共用体のすべてのメンバは、
同一のマシン表現(machine representation)を持っていなければなりません。
これは、
このような引数渡しが正常に機能するために必要です。
transparent_union
属性は、
互換性のために複数のインターフェイスを持つライブラリ関数のために設計されています。
例えば、
wait
関数が、
POSIXとの互換のためにint *
型の値を受け付けなければならず、
その一方で、
4.1BSDインターフェイスとの互換のために
union wait *
型の値を受け付けなければならないとしましょう。
もしwait
のパラメータがvoid *
型であったとすると、
wait
はどちらの引数も受け付けるでしょうが、
その他の任意のポインタ型も受け付けることになってしまい、
引数の型チェックがあまり役に立たなくなるでしょう。
こうする代わりに、
<sys/wait.h>
では、
そのインターフェイスを例えば次のように定義することができます。
typedef union { int *__ip; union wait *__up; } wait_status_ptr_t __attribute__ ((__transparent_union__)); pid_t wait (wait_status_ptr_t);このインターフェイスでは、
int *
の呼び出し規約を使って、
int *
型とunion wait *
型の引数を渡すことができます。
プログラムは、
どちらの型の引数を使ってもwait
を呼び出すことができます。
int w1 () { int w; return wait (&w); } int w2 () { union wait w; return wait (&w); }このインターフェイスでは、
wait
の実装は例えば次のようになります。
pid_t wait (wait_status_ptr_t p) { return waitpid (-1, p.__ip, 0); }
unused
union
とstruct
を含む)
型に対して指定されると、
その型の変数が存在しても、
おそらく使われないはずであるということを意味します。
その型を持つ変数が何も行わないように見えても、
GNU CCは警告メッセージを出力しません。
ロックやスレッドのクラスでこのようなことがよくあります。
これらのクラスは通常、
定義されたあと参照されませんが、
これらのクラスの持つ生成関数や消去関数の中に重要な管理機能が含まれているのです。
複数の属性を指定するには、 例えば`__attribute__ ((aligned (16), packed))'のように、 二重の丸括弧(())の中で属性をカンマで区切ります。
関数をinline
宣言することにより、
GNU CCに、
その関数のコードを呼び出し側のコードの中に組み込ませることができます。
これにより関数呼び出しのオーバーヘッドがなくなるので、
実行速度がより速くなります。
さらに、
実際の引数の値のいずれかが定数であれば、
値が既知であることを利用して
コンパイル時に単純化を行うことができるかもしれません。
この場合、
インライン関数のすべてのコードが組み込まれる必要がなくなります。
バイナリ・コードのサイズに対する影響については、
これほどはっきりと予測することができません。
インライン関数を使った場合のオブジェクト・コードは、
特定の状況に応じて、
より大きくも、
より小さくもなる可能性があります。
関数のインライン展開は最適化の1つであり、
実際には最適化コンパイルを行う場合にのみ「機能する」ものです。
`-O'を指定しなければ、
関数は実際にはまったくインライン展開されません。
関数のインライン宣言を行うには、
その宣言の中で次のようにinline
キーワードを使います。
inline int inc (int *a) { (*a)++; }
(ANSI Cのプログラムによって組み込まれるヘッダ・ファイルを書いている場合は、
inline
ではなく__inline__
と書いてください。
代替キーワードを参照してください)。
また、
`-finline-functions'オプションによって、
「十分に単純な」すべての関数をインライン関数にすることもできます。
関数定義においてある特定のことを行うと、
関数はインライン代替に適さないものになってしまうという点に注意してください。
特定のこととは、
可変引数(varargs)の使用、
allocaの使用、
可変サイズのデータ型の使用(可変長配列を参照)、
評価goto(computed goto)の使用(値としてのラベルを参照)
非局所的gotoの使用、
入れ子関数(入れ子関数を参照)などです。
`-Winline'を指定すると、
inline
指定されている関数の置き換えが行えないと、
警告メッセージが出力され、
また、
置き換えが行えない理由が出力されます。
CとObjective Cでは、
C++とは異なり、
inline
キーワードは関数の結合に影響を与えません。
GNU CCでは、
C++プログラムのクラス本体の中で定義されたメンバ関数は、
明示的にinline
宣言されていなくても、
自動的にインライン展開されます
(これは`-fno-default-inline'によって無効にすることができます。
C++の方言を制御するオプションを参照)。
ある関数がインライン関数であり、
かつ、
static
である場合、
その関数の呼び出されているところすべてにおいてコードが呼び出し側の中に組み込まれ、
その関数のアドレスが決して使われないのであれば、
その関数自体のアセンブラ・コードは決して参照されることがありません。
このような場合、
GNU CCは、
オプション`-fkeep-inline-functions'が指定されない限り、
その関数自体のアセンブラ・コードを実際には出力しません。
関数呼び出しには、
さまざまな理由のために、
組み込みのできないものがいくつかあります
(特に、
関数の定義よりも前にある関数呼び出しを組み込むことはできませんし、
定義中の再帰呼び出しも組み込むことはできません)。
組み込まれない関数呼び出しがあれば、
その関数は通常どおりアセンブラ・コードにコンパイルされます。
プログラムが関数のアドレスを参照する場合も、
その関数をインライン展開することはできないので、
関数は通常どおりコンパイルされなければなりません。
インライン関数がstatic
ではない場合、
他のソース・ファイルからその関数が呼び出されるかもしれないということを、
コンパイラは想定しなければなりません。
どのようなプログラムでも、
広域シンボルはただ一度だけしか定義できないので、
その関数は他のソース・ファイルの中で定義されてはならず、
したがって、
他のソース・ファイルの中でその関数呼び出しを組み込むことはできません。
したがって、
static
ではないインライン関数は、
常に通常どおりの方法で独立してコンパイルされます。
関数定義においてinline
とextern
をともに指定すると、
その定義はインライン展開にのみ使われます。
いかなる場合でも、
その関数が独立してコンパイルされることはありません。
その関数のアドレスを明示的に参照した場合でもそうです。
そのようなアドレスは、
あたかもその関数の宣言だけが行われ、
定義は行われなかったかのように、
外部参照となります。
このinline
とextern
の組み合わせは、
マクロとほぼ同様の効果を持ちます。
その使い方は、
ヘッダ・ファイルの中でこれらのキーワードを指定して関数定義を記述し、
ライブラリ・ファイルの中でまったく同一の関数定義を
(inline
とextern
を指定せずに)
記述するというものです。
ヘッダ・ファイルの中の定義は、
その関数のほとんどの呼び出しをインライン展開させることになります。
もし関数としての用途が必要であれば、
ライブラリの中にただ1つ存在する関数の実体を参照させます。
GNU Cは、 最適化を行わないときには関数のインライン展開を行いません。 このような場合にインライン展開を行うほうが良いのか、 あるいは、 行わないほうが良いのか、 明白ではありません。 しかし、 最適化を行わない場合に正しくインライン展開を実装するのは難しいということが分かったので、 簡単な方法を選んで、 インライン展開をしないことにしました。
asm
を使ったアセンブラ命令において、
その命令のオペランドをCの式を使って指定することができます。
このことは、
使いたいデータがどのレジスタまたはメモリ位置に保持されるのかを
推測する必要がないということを意味しています。
マシン記述(machine description)の中で使われるものとよく似た アセンブラ命令テンプレートに加えて、 個々のオペランドのオペランド拘束文字列(operand constraint string)を 指定しなければなりません。
例として、
68881のfsinx
命令の使い方を以下に示します。
asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
ここで、
angle
は入力オペランドに対応するCの式であり、
result
は出力オペランドに対応するCの式です。
どちらもオペランド拘束(operand constraint)として`"f"'が指定されていますが、
これは浮動小数点レジスタが必要であるということです。
`=f'の中の`='は、
そのオペランドが出力であることを示しています。
すべての出力オペランドのオペランド拘束には、
`='を使わなければなりません。
オペランド拘束には、
マシン記述に使われるのと同じ言語が使われます
(@xref{Constraints)。
個々のオペランドは、 オペランド拘束文字列と、 その後ろに続く丸括弧()に囲まれたCの式によって記述されます。 アセンブラのテンプレートと最初の出力オペランドの間はコロンによって区切られます。 また、 入力オペランドがある場合には、 最後の出力オペランドと最初の入力オペランドの間もコロンによって区切られます。 それぞれのオペランド・グループの中では、 カンマが個々のオペランドを区切ります。 オペランドの総数は、 10とマシン記述における任意の命令パターンが持つオペランドの最大数のうち 大きいほうが上限となります。
出力パラメータがなくて入力オペランドがある場合には、 本来出力オペランドが置かれるべき箇所に、 2つのコロンを連続して記述しなければなりません。
出力オペランド式はlvalueでなければなりません。
コンパイラはこれをチェックすることができます。
入力オペランドはlvalueでなくても構いません。
オペランドのデータ型が、
実行されている命令に対して適切であるか否かを、
コンパイラはチェックすることができません。
コンパイラはアセンブラ命令テンプレートを解析しませんし、
それが意味するところも、
そもそもそれが正当なアセンブラ命令であるのかどうかということすらコンパイラには分かりません。
拡張されたasm
機能は、
たいていの場合、
コンパイラがそもそもそういう命令が存在することすら知らないようなマシン命令に対して使われます。
出力式のアドレスを直接取ることができない場合
(例えば、
ビット・フィールドである場合)、
オペランド拘束によってレジスタを使えるようにしなければなりません。
そのようになっていれば、
GNU CCは、
そのレジスタをasm
の出力として使い、
そのあとで、
そのレジスタの内容を出力先へ格納します。
通常の出力オペランドは書き込みのみ可能でなければなりません。
GNU CCは、
これらのオペランドの命令実行前の値は無意味であり、
したがって、
その値を生成する必要はないものと想定します。
拡張されたasm
は、
入出力オペランド、
すなわち、
読み書き可能なオペランドをサポートしています。
オペランドがこのようなものであることを示すためには、
拘束文字`+'を使います。
入出力オペランドは、
出力オペランドと一緒に並べて記述します。
読み書き可能なオペランド
(または、
一部のビットのみが変更されるオペランド)
に対するオペランド拘束にレジスタを指定することができれば、
そのオペランドの機能を2つの別個のオペランド、
すなわち、
1つの入力オペランドと1つの書き込みのみ可能な出力オペランドに論理的に分割することができます。
これら2つのオペランドの間の関連は、
その命令を実行する際にその2つのオペランドは同じ箇所に存在しなければならない
と指定するオペランド拘束によって表現することができます。
両方のオペランドに対して同じCの式を使うこともできますし、
異なる式を使うこともできます。
以下では例として、
bar
を読み込みのみ可能なソース・オペランド、
foo
を読み書き可能な出力先オペランドとして、
(架空の)
`combine'命令を記述しています。
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
オペランド1のオペランド拘束`"0"'は、 オペランド1がオペランド0と同じ箇所に存在しなければならないということを指定しています。 オペランド拘束の中で数字を使うのは入力オペランドの中でのみ許され、 それは出力オペランドを参照するものでなければなりません。
オペランド拘束の中の数字だけが、
あるオペランドが別のオペランドと同じ箇所に存在することを保証することができます。
単にfoo
が両方のオペランドの値であるということだけでは、
生成されるアセンブラ・コードの中でそれらのオペランドが同じ箇所に存在することを
保証するのに十分ではありません。
以下のような記述では、
その動作はあてになりません。
asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
さまざまな最適化や再ローディングによって、
オペランド0とオペランド1が別のレジスタに置かれることがありえます。
そうしてはならない理由をGNU CCは知りません。
例えば、
コンパイラは、
foo
の値のコピーをあるレジスタの中に見つけて、
これをオペランド1に対して使う一方で、
出力オペランド0は別のレジスタに生成する
(後になって、
そのレジスタの内容をfoo
自体のアドレスにコピーする)
かもしれません。
オペランド1に対応するレジスタはアセンブラ・コードの中で言及すらされていないので、
結果は当然うまくいかないことになりますが、
GNU CCにはそのことを知る由もありません。
命令の中には、 特定のハード・レジスタの内容を破壊するものがあります。 このことを記述するには、 入力オペランドの後ろに3番目のコロンを書き、 その後ろに内容の破壊されるハード・レジスタの名前を (文字列として) 書きます。 以下に、 VAXにおける実際の例を示します。
asm volatile ("movc3 %0,%1,%2" : /* 出力オペランドを使わない */ : "g" (from), "g" (to), "g" (count) : "r0", "r1", "r2", "r3", "r4", "r5");
値の破壊されるレジスタの指定が、
入力オペランドや出力オペランドと重複すること
(例えば、
あるメンバに対するレジスタ・クラスを指定するオペランドが、
値の破壊されるレジスタの一覧に含まれること)
はエラーです。
特に注意すべきなのは、
ある入力オペランドが変更されるよう指定されているにもかかわらず、
出力として使われないのは不正であるということです。
このようなオペランドは、
いずれにしても、
入出力オペランドとして指定されなければなりません。
出力オペランドがすべて使用されないのであれば、
後に説明するように、
asm
にvolatile
を指定する必要があるでしょう。
アセンブラ・コードから特定のハードウェア・レジスタを参照するのであれば、 それらのレジスタの値が変更されるということをコンパイラに通知するために、 3番目のコロンの後ろにそれらのレジスタをおそらく指定しなければならなくなるでしょう。 アセンブラの中には、 レジスタの名前が`%'で始まるものもいくつかあります。 アセンブラ・コードの中に`%'を1つ生成するためには、 元になる入力の中では`%%'と書かなければなりません。
アセンブラ命令が 条件コード・レジスタの内容を変更する可能性があれば、 破壊されるレジスタのリストの中に`cc'を加えてください。 いくつかのマシンにおいてGNU CCは、 条件コードを特定のハードウェア・レジスタとして表わします。 `cc'はこのレジスタの名前として使えます。 その他のマシン上では、 条件コードは異なる取り扱いを受けるため、 `cc'を指定しても何の効果もありません。 しかし、 どちらの種類のマシンにおいても、 `cc'を指定しても誤りにはなりません。
アセンブラ命令が予測不可能な形でメモリ上の内容を変更するのであれば、 破壊されるレジスタのリストに`memory'を加えてください。 これによりGNU CCは、 そのアセンブラ命令のところでは、 メモリ上の値をレジスタの中にキャッシュしたまま保持することはしなくなります。
個々のアセンブラ命令を
(`\n'として書かれる)
改行によって区切るか、
または、
アセンブラがセミコロンの使用を許すのであればセミコロンによって区切ることにより、
1つのasm
テンプレートの中に複数のアセンブラ命令を入れることができます。
GNUアセンブラではセミコロンを使うことができますし、
ほとんどのUNIX上のアセンブラも同様のようです。
入力オペランドには、
内容が破壊されると指定されたレジスタは一切使われないことが保証されています。
出力オペランドのアドレスについても同様のことが保証されています。
したがって、
内容が破壊されると指定されたレジスタは好きなだけ読み書きすることができます。
以下に、
1つのテンプレートの中に複数の命令を記述した例を示します。
この例は、
サブルーチン_foo
が、
レジスタ9とレジスタ10から引数を受け取ることを想定しています。
asm ("movl %0,r9;movl %1,r10;call _foo" : /* 出力オペランドを使わない */ : "g" (from), "g" (to) : "r9", "r10");
出力オペランドに拘束変換修飾子(constraint modifier)`&'が指定されていなければ、 GNU CCは、 出力が生成されるときには既に入力は使い終わっているという想定のもとに、 まったく関係のない入力オペランドが使っているレジスタに 出力オペランドを割り当てるかもしれません。 アセンブラ・コードが実際には複数の命令から構成されている場合、 この想定は正しくない可能性があります。 そのような場合には、 入力オペランドと同じところに割り当てられてはならない 個々の出力オペランドに対して`&'を使ってください。 @xref{Modifiers。
アセンブラ命令によって生成される条件コードをテストしたいのであれば、
以下のように、
分岐命令と分岐先ラベルをasm
の中に入れなければなりません。
asm ("clr %0;frob %1;beq 0f;mov #1,%0;0:" : "g" (result) : "g" (input));
ここでは、 GNUアセンブラとほとんどのUNIX上のアセンブラがそうであるように、 アセンブラが局所ラベルをサポートしていることを想定しています。
ラベルに関しては、
あるasm
から別のasm
へのジャンプはサポートされていません。
コンパイラの最適化処理部はこのようなジャンプのことを知りません。
したがって、
どのように最適化を行うかを決定する際に、
このようなジャンプのことを考慮に入れることができません。
通常、
これらのasm
命令を使うのに最も便利な方法は、
関数のように見えるマクロの中に入れてしまうことです。
以下に例を示します。
#define sin(x) \ ({ double __value, __arg = (x); \ asm ("fsinx %1,%0": "=f" (__value): "f" (__arg)); \ __value; })
ここでは、
適切なdouble
型の値に対して命令が実行されるのを確実にするために、
また、
自動的にdouble
型に変換できるような引数x
のみを受け取るようにするために、
変数__arg
が使われています。
正しいデータ型に対して命令が実行されるのを確実にするための別の方法として、
asm
の中でキャストを使う方法があります。
これは、
変数__arg
を使う方法とは、
違いのより大きい型からでも変換を行うという点が異なります。
例えば、
要求されている型がint
である場合、
引数をint
型へキャストしてやれば、
その引数がポインタであっても文句なく受け付けてくれるでしょうが、
__arg
という名前のint
型の変数に引数を代入しようとすると、
呼び出し側で明示的にキャストが行われていない限り、
ポインタを使っていることに関して警告メッセージが出力されるでしょう。
asm
が出力オペランドを持つ場合、
GNU CCは最適化を行うために、
その命令には出力オペランドを変更すること以外に副作用はないと想定します。
このことは、
副作用を持つ命令を使うことはできないということを意味するわけではありませんが、
出力オペランドが使われないと、
コンパイラがそれらの命令を除去したり、
ループの外に移動したり、
あるいは、
2つの命令が共通部分式となる場合には、
2つの命令を1つに置き換えたりしますので、
注意しなければなりません。
また、
その副作用さえなければ変更されるようには見えないある変数に対して
命令が副作用を持つ場合、
その変数の古い値がたまたまレジスタの中にあると、
その古い値が後に再度使用されるかもしれません。
asm
の後ろにキーワードvolatile
を書くことによって、
asm
命令が除去されたり、
大きく移動されたり、
1つにまとめられたりすることを防ぐことができます。
以下に例を示します。
#define get_and_set_priority(new) \ ({ int __old; \ asm volatile ("get_and_set_priority %0, %1": "=g" (__old) : "g" (new)); \ __old; })
出力を持たないasm
命令を書くと、
GNU CCは、
その命令が副作用を持つものと理解し、
その命令を除去したりループの外へ移動したりしません。
命令の持つ副作用が純粋に外部的なものではなく、
入力の読み込み、
指定されたレジスタまたはメモリの内容の破壊とは異なる方法で
プログラムの中の変数に影響を及ぼすのであれば、
将来のバージョンのGNU CCが、
コア領域(core region)の内部で命令を移動するようになるのを予防するために、
volatile
キーワードを指定するべきでしょう。
オペランドや破壊されるレジスタを一切持たないasm
命令
(および「古い方式」のasm
)
は、
volatile
キーワードを指定した場合と同様に、
削除されることもありませんし、
到達不可能でない限り、
大きく移動されることもありません。
volatile
指定されたasm
命令であっても、
コンパイラにとっては些細な方法で移動することができます。
例えば、
ジャンプ命令の前(または後ろ)への命令の移動です。
volatile
指定されたasm
命令の順序が、
完全に連続的なままに維持されると期待することはできません。
命令を連続的に出力したいのであれば、
単一のasm
を使ってください。
アセンブラ命令が残した条件コードにアクセスする方法を探そうと考えるのは自然なことです。 しかし、 このような方法を実装しようと試みた結果、 これを確実に実行する方法がないことが分かりました。 問題になるのは、 出力オペランドが再ローディングを必要とするかもしれないという点です。 これにより、 追加的な「ストア」命令が実行されることになります。 ほとんどのマシン上では、 この追加的な命令によって、 条件コードをテストする時間のないまま条件コードが変更されることになってしまいます。 この問題は、 通常の「テスト」命令と「比較」命令では発生しません。 その理由は、 これらの命令が出力オペランドを持たないからです。
ANSI Cプログラムによって組み込まれなければならないヘッダ・ファイルを書いている場合には、
asm
ではなく__asm__
と書いてください。
代替キーワードを参照してください。
asm_operands insnの中におけるスタック的なレジスタ(stack-like regs)の使用については、 いくつかのルールがあります。 これらのルールは、 スタック的なレジスタであるオペランドに対してのみ適用されます。
asm ("foo" : "=t" (a) : "f" (b));このasmによれば、 入力Bはasmによってはポップされず、 結果はレジスタ・スタック上にプッシュされます。 すなわち、 このasmの実行後のスタックは、 実行前のスタックよりも1つだけ深くなります。 しかし、 入力Bの値がこのinsnの中で無効になるのであれば、 再ロードの過程で、 同一のレジスタを入力と出力の両方に使用できると見なされる可能性はあります。 入力オペランドの中に拘束
f
を指定されたものがあると、
すべての出力レジスタの拘束として&
が指定されなければなりません。
このasmは、
以下のように記述されることになります。
asm ("foo" : "=&t" (a) : "f" (b));
=f
のような指定は許されません。
オペランド拘束においては、
あるクラスのある特定のレジスタを選択しなければなりません。
以下に、 合理的なasmの書き方を示します。 最初のasmは、 入力を1つ取り、 その入力を内部的にポップして、 2つの出力を生成します。
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
次のasmは、
入力を2つ取り、
1つの出力で置き換えます。
入力は、
fyl2xp1
opcodeによってポップされます。
fyl2xp1
が両方の入力をポップするということを
reg-stack.cが知ることができるようにするためには、
ユーザはst(1)
を書かなければなりません。
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");
Cの関数または変数に対してアセンブラ・コードの中で使われる名前を、
以下のようにその宣言子の後ろにasm
(または__asm__
)
キーワードを書くことによって、
指定することができます。
int foo asm ("myfoo") = 2;
これは、
変数foo
に対してアセンブラ・コードの中で使われる名前が、
通常の`_foo'ではなく、
`myfoo'であると指定するものです。
Cの関数または変数の名前の前に通常はアンダースコアが付加されるシステム上においては、 この機能によって、 リンカのためにアンダースコアで始まらない名前を定義することができます。
関数定義において、
このような方法でasm
を使うことはできません。
しかし以下のように、
関数宣言を定義の前に書き、
そこにasm
を置くことによって、
同等の効果を得ることができます。
extern func () asm ("FUNC"); func (x, y) int x, y; ...
選択されたアセンブラ名が 他のどのアセンブラ・シンボルとも決して一致しないようにするのは、 その名前を選択した人の責任です。 また、 レジスタの名前を使うことは許されません。 レジスタの名前を使うと、 まったく不正なアセンブラ・コードが生成されることになります。 GNU CCでは現在のところ、 静的変数をレジスタの中に格納することはできません。 おそらくいつか、 このようなこともできるようになるでしょう。
GNU Cでは、 指定されたハードウェア・レジスタの中に少数の広域変数を置くことができます。 また、 通常のレジスタ変数が割り当てられるべきレジスタを指定することもできます。
asm
機能
(Cの式をオペランドとして持つアセンブラ命令を参照)
とともに使うと便利なことがあります
(ただし、
asm
の中においてそのオペランドに対して指定されたオペランド拘束に対して、
指定されたレジスタが適合する場合にのみ機能します)。
GNU Cでは以下のようにして、 広域レジスタ変数を定義することができます。
register int *foo asm ("a5");
ここで、
a5
は使われるべきレジスタの名前です。
ライブラリ・ルーチンがそのレジスタの内容を破壊しないように、
マシン上の関数呼び出しによって待避と復元が通常は行われるレジスタを選択してください。
レジスタの名前は当然CPUに依存しますので、
CPUの種類に応じてプログラムを条件分岐させる必要があります。
レジスタa5
は、
68000上でポインタ型の変数に対して選択すると良いでしょう。
レジスタ・ウィンドウを持つマシン上では、
関数呼び出しのメカニズムによって不思議な影響を受けることのない
「広域」レジスタを必ず選択するようにしてください。
さらに、
同一種類のCPU上でも、
オペレーティング・システムが異なるとレジスタの名前の付け方が異なるかもしれません。
このような場合には、
さらに追加の条件分岐が必要になります。
例えば、
68000上のオペレーティング・システムの中には、
先ほどのレジスタを%a5
と呼ぶものがあります。
最終的には、 レジスタを自動的に選択するようコンパイラに要求する方法が実現できるかもしれませんが、 それにはまず最初に、 コンパイラはどうやってレジスタを選択するべきか、 また、 コンパイラの選択についてユーザが指針を与えることができるようにするにはどうすればよいか、 という点を解決する必要があります。 明白な解決策はありません。
特定のレジスタにおいて広域レジスタ変数を定義すると、 少なくともカレントなコンパイル(current compilation)の中においては、 そのレジスタは完全にこの用途のために確保されてしまいます。 そのレジスタは、 カレントなコンパイルの中では、 関数の中でその他のどのような目的のためにも割り当てられることがありません。 そのレジスタは、 関数によって待避も復元もされません。 このレジスタへのストア命令は、 たとえ無意味であるように見えても、 決して削除されることはありませんが、 その値に対する参照は、 削除、 移動、 単純化の対象となる可能性があります。
シグナル・ハンドラの中や複数の制御スレッドの中から 広域レジスタ変数にアクセスするのは安全ではありません。 これは、 (実行したいタスクの専用ルーチンとして再コンパイルするのでない限り) システム・ライブラリのルーチンが、 そのレジスタを一時的に他の用途で使うかもしれないからです。
広域レジスタ変数を使うある関数の中から、
その変数に関する情報を参照せずにコンパイルされた
(すなわち、
その変数が宣言されていない別のソース・ファイルの中にある)
第3の関数lose
を経由して、
最初の関数と同様に広域レジスタ変数を使う別の関数foo
を呼び出すことは安全ではありません。
これは、
lose
がそのレジスタを待避して、
何か別の値をそこに入れるかもしれないからです。
例えば、
qsort
に渡す比較関数の中において、
広域レジスタ変数が利用可能であると期待することはできません。
というのは、
qsort
がそのレジスタの中に別の値を入れてしまうかもしれないからです
(この広域レジスタ変数を組み込んでqsort
を再コンパイルする用意があれば、
この問題を解決することができます)。
ある広域レジスタ変数を他のどのような目的にも使うことがないように、
実際にはその広域レジスタ変数を使うことのない、
qsort
やその他のソース・ファイルを再コンパイルしたいのであれば、
コンパイラ・オプション`-ffixed-reg'を指定するだけで十分です。
実際にそのソース・コードに広域レジスタ宣言を加える必要はありません。
広域レジスタ変数の値を変更する可能性のある関数は、 その広域レジスタ変数に関する情報を参照せずにコンパイルされた関数から 安全に呼び出すことはできません。 その関数からの復帰時に、 呼び出し側がそのレジスタの中に見つけることができるものと期待している値が 破壊されかもしれないからです。 したがって、 広域レジスタ変数を使うプログラム部分への入口になっている関数は、 呼び出し側に所属する値の待避と復元を明示的に行わなければなりません。
ほとんどのマシン上においてlongjmp
は、
個々の広域レジスタ変数の値を、
setjmp
が呼び出された時点における値に復元します。
しかしマシンの中には、
longjmp
が広域レジスタ変数の値を変更しないものもあります。
移植性を持たせるためには、
setjmp
を呼び出した関数が追加的な調整作業を実行することにより、
広域レジスタ変数の値が待避され、
かつ、
longjmp
の中でその値が復元されるようにするべきです。
これによって、
longjmp
の処理内容にかかわらず、
同一の結果がもたらされることになります。
すべての広域レジスタ変数の宣言は、 すべての関数定義に先立って行われなければなりません。 広域レジスタ変数の宣言が関数定義の後ろにあると、 その宣言よりも前にある関数によってそのレジスタが他の目的で使われてしまうのを防ぐのに 手遅れになってしまうでしょう。
実行ファイルからレジスタに対して初期値を提供するための手段は存在しないので、 広域レジスタ変数は初期値を持つことができません。
Sparc上では、
g3 ... g7が適切なレジスタであるという報告がされていますが、
商と剰余を求めるサブルーチンのほかに、
getwd
のようないくつかのライブラリ関数が、
g3とg4の値を変更します。
g1とg2は一時的な用途で使われる局所的なレジスタです。
68000上では、 a2 ... a5およびd2 ... d7が適切なはずです。 もちろん、 これらのうち実際に使うのは少数にとどめるべきです。
以下のようにして、 指定したレジスタによって局所レジスタ変数を定義することができます。
register int *foo asm ("a5");
ここで、
a5
は使われるべきレジスタの名前です。
これは広域レジスタ変数を定義するのに使った構文と同一ですが、
局所変数の場合、
定義は関数の内部に置かれるという点に注意してください。
レジスタの名前は当然CPUに依存しますが、 このような依存性は問題になりません。 ほとんどの場合、 特定のレジスタを指定することは、 明示的なアセンブラ命令(Cの式をオペランドとして持つアセンブラ命令を参照)において役に立つものだからです。 こうした事情により通常は、 CPUの種類に応じてプログラムを条件分岐させる必要があります。
さらに、
同一種類のCPU上でも、
オペレーティング・システムが異なるとレジスタの名前の付け方が異なるかもしれません。
このような場合には、
さらに追加の条件分岐が必要になります。
例えば、
68000上のオペレーティング・システムの中には、
先ほどのレジスタを%a5
と呼ぶものがあります。
このようなレジスタ変数を定義しても、 レジスタは確保されません。 フロー制御処理部がその変数の値が無効であると決定したところでは、 そのレジスタを他の用途に利用することができます。 しかし、 このようなレジスタは、 再ロードのパスにおいて利用できません。 この機能を過度に使いすぎると、 いくつかの関数をコンパイルする際にコンパイラが利用できるレジスタの数が 非常に少なくなってしまいます。
このオプションを使っても、
GNU CCの生成するコードにおいて、
この変数が常に指定されたレジスタの中にあることが保証されるわけではありません。
asm
文の中においてこのレジスタを明示的に参照するようなコーディングをして、
そのレジスタは常にこの変数を参照していると想定してはいけません。
局所レジスタ変数へのストア命令は、 データフロー解析によって無意味であると判明した場合には、 削除されることがあります。 局所レジスタ変数への参照は、 削除、 移動、 単純化の対象となる可能性があります。
オプション`-traditional'を使うと、
特定のキーワードが利用できなくなります。
また、
オプション`-ansi'を使うと、
別の特定のキーワードが利用できなくなります。
ANSI Cのプログラムや伝統的なCのプログラムも含むすべてのプログラムにおいて
利用可能でなければならない汎用的なヘッダ・ファイルの中で、
GNU Cの拡張機能やANSI Cの機能を使いたい場合に、
これが問題になります。
キーワードasm
、
typeof
、
inline
は、
`-ansi'を指定してコンパイルされるプログラムの中では問題があるので使うことができません。
その一方で、
キーワードconst
、
volatile
、
signed
、
typeof
、
inline
は、
`-traditional'を指定してコンパイルされるプログラムの中では問題があります。
この問題を解決する方法は、
問題のある個々のキーワードの前後に`__'を付けることです。
例えば、
asm
の代わりに__asm__
を、
const
の代わりに__const__
を、
inline
の代わりに__inline__
を使ってください。
他のCコンパイラは、 このような代替キーワードを受け付けてくれません。 別のコンパイラでコンパイルを行いたいのであれば、 代替キーワードをマクロとして定義して、 慣習的なキーワードと置き換えることができます。 これは、 以下のようになります。
#ifndef __GNUC__ #define __asm__ asm #endif
`-pedantic'を指定すると、
多くのGNU C拡張機能に対して警告メッセージが出力されます。
式の前に__extension__
と書くことによって、
ある式の中における警告メッセージの出力を防ぐことができます。
__extension__
にはこれ以外の作用はありません。
enum
型
enum
タグを、
それが持つことのできる値を指定せずに定義することができます。
これは、
struct foo
と書いてその要素を記述しないようなもので、
不完全な型になります。
後に、
それが持つことのできる値を指定する宣言をすることによって、
その型は完全なものになります。
この型が不完全な間は、 それを使って変数や記憶域を割り当てることはできません。 しかし、 その型へのポインタを操作することはできます。
この拡張機能はあまり役に立たないかもしれませんが、
これによって、
enum
の処理とstruct
やunion
の処理の一貫性がより高くなります。
この拡張機能はGNU C++ではサポートされていません。
GNU CCでは、
カレントな関数の名前を値として持つ2つの文字列変数があらかじめ定義されています。
変数__FUNCTION__
は、
ソース・コードの中に記述されたとおりの関数名です。
一方、
変数__PRETTY_FUNCTION__
は、
言語固有のスタイルできれいに出力された(pretty printed)関数名です。
この2つの名前はCの関数では常に同一ですが、 C++の関数の場合は異なることがあります。 例えば、
extern "C" { extern int printf (char *, ...); } class a { public: sub (int i) { printf ("__FUNCTION__ = %s\n", __FUNCTION__); printf ("__PRETTY_FUNCTION__ = %s\n", __PRETTY_FUNCTION__); } }; int main (void) { a ax; ax.sub (0); return 0; }
というプログラムを実行すると、 次のような出力が得られます。
__FUNCTION__ = sub __PRETTY_FUNCTION__ = int a::sub (int)
これらの名前はマクロではなく、
あらかじめ定義された文字列変数です。
例えば、
`#ifdef __FUNCTION__'は関数の中では特別な意味を持ちません。
これは、
識別子__FUNCTION__
について、
プリプロセッサが特別なことを何も行わないからです。
ある関数の呼び出し側に関する情報を入手するために以下の関数を使うことができます。
__builtin_return_address (level)
0
を指定すると、
カレントな関数の復帰アドレスが返ってきます。
値1
を指定すると、
カレントな関数を呼び出した関数の復帰アドレスが返ってきます。
以下、
同様です。
引数levelは整数の定数でなければなりません。
マシンの中には、
カレントな関数以外の関数の復帰アドレスを決定することが不可能なものがあります。
そのような場合、
あるいは、
スタックのトップに達してしまった場合には、
この関数は0
を返します。
この関数をデバッグの目的で使う際には、
引数には0以外の値だけを指定するべきです。
__builtin_frame_address (level)
__builtin_return_address
に似ていますが、
関数の復帰アドレスではなく関数フレームのアドレスを返します。
値0
を指定して__builtin_frame_address
を呼び出すと、
カレントな関数のフレーム・アドレスが返ってきます。
値1
を指定すると、
カレントな関数を呼び出した関数のフレーム・アドレスが返ってきます。
以下、
同様です。
フレームとは、
局所変数や待避されたレジスタを保持している、
スタック上の領域のことです。
通常、
フレーム・アドレスとは、
関数によって最初にスタックにプッシュされたワードのアドレスのことです。
しかし、
正確な定義は、
プロセッサと呼び出し規約に依存します。
プロセッサが専用のフレーム・ポインタ・レジスタを持つ場合、
関数がフレームを持っていると、
__builtin_frame_address
はフレーム・ポインタ・レジスタの値を返します。
__builtin_return_address
にあてはまる注意事項は、
この関数にもあてはまります。
GNU CCは、 これまで説明してきたもの以外にも、 数多くの組み込み関数を提供しています。 その中のいくつかは、 例外や可変個数引数の処理のために内部的に使われるもので、 ときおり変更される可能性があるため、 ここでは説明しません。 これらの関数を一般的に使用することはお勧めしません。
これら以外の関数は、 最適化のために提供されています。
GNU CCには、
標準Cライブラリの多くの関数の組み込み版が含まれています。
これらは、
`-fno-builtin'(Cの方言を制御するオプションを参照)オプションが指定された場合でも、
Cのライブラリ関数と同一の意味を持つものとして常に扱われます。
Cのライブラリ関数
alloca
、
ffs
、
abs
、
fabsf
、
fabs
、
fabsl
、
labs
、
memcpy
、
memcmp
、
strcmp
、
strcpy
、
strlen
、
sqrtf
、
sqrt
、
sqrtl
、
sinf
、
sin
、
sinl
、
cosf
、
cos
、
cosl
に対応します。
コンパイル時に、
ある値が定数であることが分かっていて、
その値を含む式に対してGNU CCが定数計算(constant-folding)を実行することが
可能かどうかを知るために、
組み込み関数__builtin_constant_p
を使うことができます。
この関数の引数は、
テストするべき値です。
関数の戻り値は、
引数がコンパイル時に定数であることが分かっている場合には整数の1であり、
そのことが分からない場合には0です。
戻り値が0であっても、
その値が定数ではないという意味ではありません。
単に、
指定された`-O'オプションの値においては、
それが定数であるということをGNU CCが証明できないというだけのことです。
この関数は、 メモリが極めて重要なリソースである組み込みアプリケーションにおいて、 典型的に使われるでしょう。 複雑な計算処理がある場合、 それが定数の間の計算であれば、 あらかじめ計算してしまいたいでしょう。 しかし、 それ以外の場合は関数を呼び出す必要があります。
#define Scale_Value(X) \ (__builtin_constant_p (X) ? ((X) * SCALE + OFFSET) : Scale (X))
この組み込み関数は、 マクロとインライン関数のどちらの中でも使うことができます。 しかし、 これをインライン関数の中で使い、 その関数への引数を組み込み関数への引数として渡す場合、 インライン関数を文字列定数または生成関数式(生成関数式を参照)を引数として呼び出すと、 GNU CCは決して1を返すことはありません。 また、 `-O'オプションを指定しない限り、 インライン関数に定数の数値を渡すと1を返しません。
過去において、 まだC++言語が発展途上にあった頃、 新しい機能を実験するために、 GNU C++コンパイラが拡張されたことがありました。 C++標準が完成した現在では、 これらの機能のいくつかは、 より優れた代替機能によって取って代わられました。 古い機能を使うと、 将来その機能は削除されるという警告メッセージが出力される場合もあります。 場合によっては、 機能自体が既に削除されてしまっているところもあります。
以下のリストは、 網羅的ではありませんが、 使用することに賛成できないオプションのいくつかを説明してあります。
-fthis-is-variable
-fexternal-templates
-falt-external-templates