[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


C 言語ファミリに対する拡張機能

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);
}

ここで、 関数intermediatestoreのアドレスを引数として受け取っています。 intermediatestore を呼び出すと、 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)
この組み込み関数は、 argumentsvoid *型)とsizeint型) によって表わされるパラメータのコピーを使って、 functionvoid (*)()型)を呼び出します。 argumentsの値は、 __builtin_apply_argsによって返された値でなければなりません。 引数sizeは、 スタック上の引数データのサイズをバイト単位で指定します。 この関数は、 functionが返した値が何であれ、 その値を返す方法を記述するデータへのvoid *型のポインタを返します。 そのデータは、 スタック上に割り当てられたメモリ・ブロックの中に待避されます。 sizeに指定するのに適切な値を計算することは、 常に簡単なことであるとは限りません。 この値は、 入力引数の領域からコピーされてスタックにプッシュされるべきデータの量を計算するために、 __builtin_applyによって使われます。
__builtin_return (result)
この組み込み関数は、 resultによって記述される値を戻り値として、 この組み込み関数を包含している関数から復帰します。 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; })

局所変数に対してアンダースコアで始まる名前を使う理由は、 abが置き換えられる式の中で使われている変数の名前との衝突を回避するためです。 最終的には、 初期化子の後ろからスコープが開始されるような変数を宣言することを可能にする、 新しい形式の宣言構文を設計したいと考えています。 こちらの方が、 名前の衝突を防ぐ手段としてはより信頼性のあるものになるでしょう。

typeofによる型への参照

式の型を参照する別の方法にtypeofがあります。 このキーワードを使うときの構文はsizeofの構文に似ていますが、 意味論的には、 typedefにより定義された型名のような働きをします。

typeofへの引数を記述する方法は2つあります。 1つは式を使う方法で、 もう1つは型を使う方法です。 以下に、 式を使う方法の例を示します。

typeof (x[0](1))

ここでは、 xが関数の配列であると仮定しています。 この例によって表現される型は、 関数の戻り値の型です。

次に、 型名を引数に使う例を示します。

typeof (int *)

この例では、 表現される型はintへのポインタ型です。

ANSI Cのプログラムにインクルードされたときに 問題なく使えなければならないヘッダ・ファイルを書いている場合は、 typeofの代わりに__typeof__と書いてください。 代替キーワードを参照してください。

typeofは、 typedef名が使えるところであればどこでも使うことができます。 例えば、 宣言、 キャスト、 sizeofの中、 typeofの中でそれを使うことができます。

一般化されたlvalue

複合式(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$realfoo$imagという名前が与えられます。 デバッガを使って、 これら2つの架空の変数の値を調べたり、 値を設定したりすることができます。

将来のGDBバージョンでは、 こうした実数部と虚数部のペアを認識できるようになり、 それらを複素数型の単一の変数として扱うようになるでしょう。

16進形式の浮動小数

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の持つあいまいさを解決することができなくなります。 ffloat型の浮動小数点定数の拡張子でもあるため、 0x1.fは、 1.0f1.9375のどちらの意味にもなりえます。

長さが0の配列

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ではない配列の添字

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 foostructureが以下に示すように宣言されているものとしましょう。

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ラベルの中で、 ある連続した値の範囲を指定することができます。 以下に例を示します。

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;

xyは両方とも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個の属性noreturnconstformatno_instrument_functionsectionconstructordestructorunusedweakが関数に対して定義されています。 sectionを含むその他の属性が、 変数宣言 (変数属性の指定を参照) と型 (型属性の指定を参照) に対してサポートされています。

個々のキーワードの前後に`__'を付けて属性を指定することもできます。 これにより、 同じ名前を持つマクロが定義済みであるような事態を心配することなく、 ヘッダ・ファイルの中でキーワードを使うことができるようになります。 例えば、 noreturnの代わりに__noreturn__を使うことができます。

noreturn
標準ライブラリ関数の中には、 abortexitのように、 復帰(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
多くの関数は、 引数以外の値を一切参照せず、 その戻り値以外に何の作用も持ちません。 このような関数には、 算術演算子と同様、 共通部分式の除去(common subexpression elimination)や ループの最適化を適用することができます。 このような関数は、 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属性は、 その関数が、 書式文字列に照らし合わせて型チェックを行うべき引数、 すなわち、 printfscanfstrftimeのような方式の引数を取ることを指定します。 例えば、
extern int
my_printf (void *my_object, const char *my_format, ...)
      __attribute__ ((format (printf, 2, 3)));
という宣言を行うと、 コンパイラは、 printf方式の書式文字列引数であるmy_formatを使って、 my_printfの呼び出しにおける引数の整合性をチェックします。 archetypeパラメータは、 書式文字列がどのように解釈されるかを決定するもので、 printfscanfstrftimeのいずれかでなければなりません。 string-indexパラメータは、 どの引数が書式文字列引数であるかを (1から始まる数で) 指定します。 一方、 first-to-checkは、 書式文字列に照らし合わせてチェックするべき最初の引数の番号です。 (vprintfのように) チェックされるべき引数を指定できない関数に対しては、 第3パラメータに0を指定してください。 この場合、 コンパイラは、 書式文字列だけを対象にして整合性のチェックを行います。 上の例では、 書式文字列 (my_format) は関数my_printfの第2引数であり、 チェックするべき引数は第3引数から始まっています。 したがって、 format属性の正しいパラメータは2と3です。 書式文字列を引数として取るユーザ独自の関数を、 format属性を使って指定することができます。 これによってGNU CCは、 その関数呼び出しにおける誤りをチェックできるようになります。 ANSIライブラリ関数のprintffprintfsprintfscanffscanfsscanfstrftimevprintfvfprintfvsprintfについては、 (`-Wformat'を使って) そのような警告が要求されているときにはいつでも、 コンパイラは書式のチェックを行います。 したがって、 ヘッダ・ファイル`stdio.h'を修正する必要はありません。
format_arg (string-index)
format_arg属性は、 その関数がprintfscanfのような方式の引数を取り、 それを修正 (例えば、他の言語に翻訳) した後に、 printfscanfのような関数に渡すということを指定します。 例えば、
extern char *
my_dgettext (char *my_domain, const char *my_format)
      __attribute__ ((format_arg (2)));
という宣言を行うと、 コンパイラは、 my_dgettextの呼び出しにおける引数を、 printf方式の書式文字列引数であるmy_formatに照らし合わせ、 整合性のチェックを行います。 このmy_dgettextの呼び出しの結果は、 printfscanfstrftimeのような方式の関数に渡されます。 string-indexパラメータは、 どの引数が書式文字列引数であるかを (1から始まる数で) 指定します。 書式文字列を変更するようなユーザ独自の関数がある場合に、 format-arg属性を使って、 そのような関数であるということを指定することができます。 これにより、 関数printfscanfstrftimeのオペランドが、 そのような独自関数への呼び出しになっている場合に、 これをチェックすることができます。 コンパイラは常に、 gettextdgettextdcgettextをこのように取り扱います。
no_instrument_function
`-finstrument-functions'が指定されていると、 ユーザによってコンパイルされるほとんどの関数の入口と出口において、 関数呼び出しのプロファイル処理を行うためのコードが生成されることになります。 この属性を持つ関数については、 そのようなコードの生成は行われません。
section ("section-name")
通常、 コンパイラは、 生成したバイナリ・コードをtextセクションに置きます。 しかしときには、 別のセクションを追加したり、 特定の関数を特別なセクションに置くことが必要になることがあります。 section属性は、 関数が特定のセクション内に置かれるよう指定します。 例えば、
extern void foobar (void) __attribute__ ((section ("bar")));
という宣言は、 関数foobarbarセクションに置きます。 ファイル形式によっては、 セクションの任意指定がサポートされていないものもあります。 したがってsection属性は、 すべてのプラットフォームで利用可能なわけではありません。 あるモジュールのすべての内容を特定のセクションにマップする必要がある場合には、 この属性ではなく、 リンカの機能を使うことを検討してください。
constructor
destructor
constructor属性を指定された関数は、 main ()関数が実行される前に、 自動的に呼び出されるようになります。 同様に、 destructor属性を指定された関数は、 main ()の実行が完了した後、 または、 exit ()が呼び出された後に、 自動的に呼び出されるようになります。 これらの属性を持つ関数は、 プログラムが実行される間に暗黙のうちに使われるデータを初期化するのに役に立ちます。 これらの属性は、 現在のところObjective Cでは実装されていません。
unused
この属性が関数に対して指定されると、 その関数はおそらく使われないはずであるという意味になります。 GNU CCは、 このような関数に対しては警告メッセージを出力しません。 C++ではパラメータを持たない定義は正当なので、 現在のところGNU C++はこの属性をサポートしていません。
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
`-fcheck-memory-usage'が指定されていると、 ほとんどのメモリ・アクセスの前に、 サポート・ルーチンの呼び出しを行うコードが生成されます。 このサポート・コードの中で、 メモリ使用の記録と、 初期化されていない記憶域、 または、 割り当てられていない記憶域の使用の検出が可能です。 asm文の使用は、 コンパイラが正しく処理することができないので、 許されません。 関数にこの属性を指定すると、 その関数については、 メモリ・チェックを行うコードは無効化されます。 これにより、 異なるオプションを使って別個にコンパイルすることなく asm文を使うことができるようになります。 また、 もしそうしたいのであれば、 ユーザ独自のサポート・ルーチンを書くこともできます。 このオプションを指定してコンパイルしても、 それが無限再帰に陥ることはありません。
regparm (number)
Intel 386上では、 regparm属性により、 コンパイラは最高でnumberで指定される個数までの整数引数を、 スタックではなくEAXEDXECXレジスタに入れて渡すようになります。 可変個数の引数を取る関数の場合は、 この属性を指定されても、 スタック上においてすべての引数を渡されます。
stdcall
Intel 386上では、 stdcall属性により、 関数が可変個数の引数を取るのでない限り、 引数を渡すのに使われたスタック領域は呼び出された関数がポップするものと、 コンパイラは想定するようになります。 Windows NT用のPowerPCコンパイラは、 現在のところstdcall属性を無視します。
cdecl
Intel 386上では、 cdecl属性により、 引数を渡すのに使われたスタック領域は呼び出した関数がポップするものと、 コンパイラは想定するようになります。 これは、 `-mrtd'オプションの効果を無効にするのに役に立ちます。 Windows NT用のPowerPCコンパイラは、 現在のところcdecl属性を無視します。
longcall
RS/6000とPowerPC上では、 longcall属性により、 コンパイラは常にポインタを使ってその関数を呼び出すようになります。 これにより、 カレントな位置から64メガバイト (67,108,864バイト) を超えて離れた位置にある関数でも呼び出すことができます。
dllimport
Windows NTを実行しているPowerPC上では、 dllimport属性により、 コンパイラは、 Windows NTのDLLによりセットアップされた関数ポインタを指し示す広域ポインタを使って その関数を呼び出すようになります。 ポインタの名前は、 __imp_と関数名を結合することにより作られます。
dllexport
Windows NTを実行しているPowerPC上では、 dllexport属性により、 コンパイラは、 その関数をdllimport属性を使って呼び出すことができるように、 関数ポインタへの広域ポインタを提供するようになります。 ポインタの名前は、 __imp_と関数名を結合することにより作られます。
exception (except-func [, except-arg])
Windows NTを実行しているPowerPC上では、 exception属性により、 コンパイラは、 宣言された関数のために出力する構造化された例外テーブルのエントリを変更するようになります。 except-funcに指定される文字列または識別子が、 構造化された例外テーブルの第3のエントリに入れられます。 それは、 例外が発生したときに例外処理メカニズムにより呼び出される関数を表わします。 except-argに文字列または識別子が指定されると、 それは構造化された例外テーブルの第4のエントリに入れられます。
function_vector
このオプションは、 H8/300とH8/300H上において、 指定された関数が関数ベクタを利用して呼び出されるべきであることを示すのに使います。 関数ベクタを利用して関数呼び出しを行うとコード・サイズは小さくなります。 しかし、 関数ベクタのサイズには上限 (H8/300では128エントリ、 H8/300Hでは64エントリ) があり、 割り込みベクタとの間で領域が共用されます。 このオプションを正常に機能させるためには、 GNU binutilsバージョン2.7以降のGASとGLDを使用しなければなりません。
interrupt_handler
このオプションは、 H8/300とH8/300H上において、 指定された関数が割り込みハンドラであることを示すのに使います。 この属性が指定されていると、 コンパイラは、 その関数の入口と出口において、 割り込みハンドラの中で使うのに適した命令シーケンスを生成します。
eightbit_data
このオプションは、 H8/300とH8/300H上において、 指定された変数が8ビット・データ・セクションに置かれるべきであることを示すのに使います。 コンパイラは、 8ビット・データ域内のデータへの特定の操作に対して、 より効率的なコードを生成するようになります。 8ビット・データ域のデータは、 256バイトまでに制限されていることに注意してください。 このオプションを正常に機能させるためには、 GNU binutilsバージョン2.7以降のGASとGLDを使用しなければなりません。
tiny_data
このオプションは、 H8/300H上において、 指定された変数がタイニ(tiny)・データ・セクションに置かれるべきであることを示すのに使います。 コンパイラは、 タイニ・データ・セクション内におけるデータのロードとストアに対して、 より効率的なコードを生成するようになります。 タイニ・データ域のデータは、 32Kバイトより若干少ないサイズまでに制限されていることに注意してください。
interrupt
このオプションは、 M32R/D上において、 指定された関数が割り込みハンドラであることを示すのに使います。 この属性が指定されていると、 コンパイラは、 その関数の入口と出口において、 割り込みハンドラの中で使うのに適した命令シーケンスを生成します。
model (model-name)
この属性は、 M32R/D上において、 オブジェクトのアドレス範囲(addressability)を設定し、 関数に対して生成されるコードを決定するのに使います。 識別子model-nameは、 smallmediumlargeのいずれかであり、 コード・モデルの1つを表わします。 スモール・モデルのオブジェクトは (そのアドレスがld24命令によってロードできるように) メモリの低位16MBの範囲内にあり、 bl命令によって呼び出すことができます。 ミディアム・モデルのオブジェクトは、 32ビット・アドレス空間の任意の位置にあり (そのアドレスをロードするために、 コンパイラはseth/add3命令を生成します)、 bl命令によって呼び出すことができます。 ラージ・モデルのオブジェクトは、 32ビット・アドレス空間の任意の位置にあり (そのアドレスをロードするために、 コンパイラはseth/add3命令を生成します)、 かつ、 bl命令では到達不可能なことがあります (その場合、 コンパイラは、 はるかに遅いseth/add3/jl命令シーケンスを生成します)。

二重の丸括弧(())の内部において属性をカンマで区切るか、 属性宣言の直後に別の属性宣言を記述することによって、 宣言の中において複数の属性を指定することができます。

ANSI Cの#pragmaを使うべきであるとして、 __attribute__機能に反対する人々がいます。 ANSI Cの#pragmaを使わない理由は2つあります。

  1. マクロから#pragmaコマンドを生成することは不可能です。
  2. 同一の#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の型が shortintlongのいずれであるかが分からないからです。 そこでGNU Cでは、 このような場合に、 プロトタイプがそれより後ろにある古い方式の定義を無効にすることができるようにしています。 より正確に言うと、 GNU Cでは、 関数プロトタイプにおける引数の型と、 それよりも後ろにある古い方式の定義によって指定される引数の汎整数拡張される前の型が 同一である場合、 前者が後者の汎整数拡張後の型を無効にします。 よってGNU Cにおいては、 上記の例は以下と同等となります。

int isroot (uid_t);

int
isroot (uid_t x)
{
  return x == 0;
}

GNU C++は古い方式の関数定義をサポートしていないので、 この拡張機能は無関係です。

C++方式のコメント

GNU Cでは、 `//'により始まり行末まで続く、 C++方式のコメントを使うことができます。 他の多くのCの実装でもこのようなコメントを使うことができます。 これは、 将来のCの標準仕様に含まれそうです。 しかし、 `-ansi'または`-traditional'を指定した場合には、 C++方式のコメントは認識されません。 これは、 C++方式のコメントが、 dividend//*comment*/divisorのような古くから使われている書き方と 両立しないからです。

識別子の名前の中のドル記号

GNU Cでは通常、 識別子の名前の中でドル記号を使うことができます。 これは、 古くからあるCの多くの実装においても、 そのような識別子を使うことができるからです。 しかし、 識別子の中のドル記号がサポートされないターゲット・マシンも少しあります。 その典型的な理由は、 ターゲット・アセンブラが識別子の中のドル記号を許さないということです。

定数の中のESC文字

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個の属性 alignedmodenocommonpackedsectiontransparent_unionunusedweakが変数に対して定義されています。 その他の属性が、 関数(関数属性の宣言)および型(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)
この属性は、 宣言のデータ型を指定します。 指定される型は、 モードmodeに対応する型です。 これを使うと、 事実上、 modeの長さ(width)に応じた 整数型または浮動小数点型を要求することになります。 モードが1バイトの整数型に対応することを示すために、 モードとして`byte'または`__byte__'を指定することもできます。 同様に、 1ワードの整数型に対応するモードについては `word'または`__word__'を、 ポインタを表わすために使われるモードについては `pointer'または`__pointer__'を、 それぞれ指定することができます。
nocommon
この属性は、 変数を「共通ブロック」に置かず、 その変数のための領域を直接割り当てるよう、 GNU CCに対して要求します。 `-fno-common'フラグを指定すると、 GNU CCはすべての変数に対してこのような処理を実行します。 変数に対してnocommon属性を指定すると、 その変数は0により初期化されます。 変数は、 1つのソース・ファイルの中でのみ初期化することができます。
packed
packed属性は、 aligned属性によってより大きな値が指定されていない限り、 変数または構造体フィールドが、 可能な境界整列の値のうち最小の値を取るべきであることを指定します。 この最小値は、 変数については1バイトであり、 フィールドについては1ビットです。 以下に示す構造体では、 フィールドxにはpacked属性が指定されているため、 aの直後に置かれます。
struct foo
{
  char a;
  int x[2] __attribute__ ((packed));
};
section ("section-name")
コンパイラは通常、 生成するオブジェクトをdatabssといったセクションに置きます。 しかしときには、 例えば特殊なハードウェアにマップするために、 追加のセクションが必要になったり、 特定の変数を特殊なセクションに置くことが必要になったりします。 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
共用体型の関数パラメータに対して指定されるこの属性は、 そのパラメータに対応する引数自体はその共用体の任意のメンバの型を持つことができるが、 その引数がその関数に渡される際には、 共用体の1番目のメンバの型を持つものとして扱われるということを意味します。 詳細については、 型属性の指定を参照してくださいを参照してください。 共用体データ型に対するtypedefにこの属性を使うこともできます。 この場合、 その型を持つすべての関数パラメータに対して、 この属性が適用されます。
unused
変数に対して指定されるこの属性は、 その変数はおそらく使われないはずであるということを意味します。 GNU CCは、 その変数については警告メッセージを出力しません。
weak
weak属性については、 関数属性の宣言を参照してくださいにおいて説明されています。
model (model-name)
この属性は、 M32R/D上においてオブジェクトのアドレス範囲(addressability)を設定するのに使います。 識別子model-nameは、 smallmediumlargeのいずれかであり、 それぞれのコード・モデルを表わします。 スモール・モデルのオブジェクトは (そのアドレスがld24命令によってロードできるよう) メモリの低位16MBの範囲内に存在します。 ミディアム・モデルのオブジェクトとラージ・モデルのオブジェクトは、 32ビット・アドレス空間の任意の位置に存在することができます (そのアドレスをロードするために、 コンパイラはseth/add3命令を生成します)。

複数の属性を指定するには、 例えば`__attribute__ ((aligned (16), packed))'のように、 二重の丸括弧(())の中で属性をカンマで区切ります。

型属性の指定

キーワード__attribute__により、 struct型とunion型を定義する際に、 それらの型に特殊な属性を指定することができます。 このキーワードの後ろに、 二重の丸括弧(())に囲まれた属性指定が続きます。 現在、 3個の属性 alignedpackedtransparent_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型の境界整列の値は、 少なくとも、 そのstructunionのすべてのメンバの境界整列の値の 最小公倍数の完全倍数になっていなければならないという点に注意してください。 このことは、 structまたはunionの任意の1つのメンバに aligned属性を指定することによって、 structunionの境界整列を効果的に調整することが可能である ということを意味しています。 しかし、 上の例において示される表記法は、 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
enumstructunion の型定義に指定されるこの属性は、 その型を表わすのに最小限必要とされるメモリだけを使うよう指定します。 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
この属性は、 (unionstructを含む) 型に対して指定されると、 その型の変数が存在しても、 おそらく使われないはずであるということを意味します。 その型を持つ変数が何も行わないように見えても、 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ではないインライン関数は、 常に通常どおりの方法で独立してコンパイルされます。

関数定義においてinlineexternをともに指定すると、 その定義はインライン展開にのみ使われます。 いかなる場合でも、 その関数が独立してコンパイルされることはありません。 その関数のアドレスを明示的に参照した場合でもそうです。 そのようなアドレスは、 あたかもその関数の宣言だけが行われ、 定義は行われなかったかのように、 外部参照となります。

このinlineexternの組み合わせは、 マクロとほぼ同様の効果を持ちます。 その使い方は、 ヘッダ・ファイルの中でこれらのキーワードを指定して関数定義を記述し、 ライブラリ・ファイルの中でまったく同一の関数定義を (inlineexternを指定せずに) 記述するというものです。 ヘッダ・ファイルの中の定義は、 その関数のほとんどの呼び出しをインライン展開させることになります。 もし関数としての用途が必要であれば、 ライブラリの中にただ1つ存在する関数の実体を参照させます。

GNU Cは、 最適化を行わないときには関数のインライン展開を行いません。 このような場合にインライン展開を行うほうが良いのか、 あるいは、 行わないほうが良いのか、 明白ではありません。 しかし、 最適化を行わない場合に正しくインライン展開を実装するのは難しいということが分かったので、 簡単な方法を選んで、 インライン展開をしないことにしました。

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");

値の破壊されるレジスタの指定が、 入力オペランドや出力オペランドと重複すること (例えば、 あるメンバに対するレジスタ・クラスを指定するオペランドが、 値の破壊されるレジスタの一覧に含まれること) はエラーです。 特に注意すべきなのは、 ある入力オペランドが変更されるよう指定されているにもかかわらず、 出力として使われないのは不正であるということです。 このようなオペランドは、 いずれにしても、 入出力オペランドとして指定されなければなりません。 出力オペランドがすべて使用されないのであれば、 後に説明するように、 asmvolatileを指定する必要があるでしょう。

アセンブラ・コードから特定のハードウェア・レジスタを参照するのであれば、 それらのレジスタの値が変更されるということをコンパイラに通知するために、 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__と書いてください。 代替キーワードを参照してください。

i386浮動小数asmオペランド

asm_operands insnの中におけるスタック的なレジスタ(stack-like regs)の使用については、 いくつかのルールがあります。 これらのルールは、 スタック的なレジスタであるオペランドに対してのみ適用されます。

  1. asm_operandsの中に、 その値が意味を持たなくなる一群の入力レジスタがある場合、 そのうちのどれがasmによって暗黙のうちにポップされ、 どれがgccによって明示的にポップされなければならないかを 知る必要があります。 asmによって暗黙のうちにポップされる入力レジスタは、 拘束によって出力オペランドと一致しているのでない限り、 その値は明示的に破棄されなければなりません。
  2. asmによって暗黙のうちにポップされる入力レジスタすべてについて、 そのポップ処理を補正するためのスタックの調整方法を知っている必要があります。 暗黙のうちにポップされないレジスタの中に、 暗黙のうちにポップされるレジスタよりも、 レジスタ・スタックのトップに近い位置に存在するものがあると、 スタックのレイアウトがどのようなものであったかを知ることは不可能になってしまうでしょう。 スタックの残りの部分を「移動」させる方法は明確ではありません。 暗黙のうちにポップされるすべての入力レジスタは、 暗黙のうちにポップされない他のすべての入力レジスタよりも、 レジスタ・スタックのトップに近い位置に存在しなければなりません。 insnの中で入力レジスタの値が無効になれば、 再ロードの過程で、 その入力レジスタが出力の再ロードに使用されることはありえます。 以下の例を考えてみてください。
    asm ("foo" : "=t" (a) : "f" (b));
    
    このasmによれば、 入力Bはasmによってはポップされず、 結果はレジスタ・スタック上にプッシュされます。 すなわち、 このasmの実行後のスタックは、 実行前のスタックよりも1つだけ深くなります。 しかし、 入力Bの値がこのinsnの中で無効になるのであれば、 再ロードの過程で、 同一のレジスタを入力と出力の両方に使用できると見なされる可能性はあります。 入力オペランドの中に拘束fを指定されたものがあると、 すべての出力レジスタの拘束として&が指定されなければなりません。 このasmは、 以下のように記述されることになります。
    asm ("foo" : "=&t" (a) : "f" (b));
    
  3. オペランドの中には、 スタック上の特定の位置に存在する必要のあるものがあります。 すべての出力オペランドは、 この範疇に入ります。 ユーザが拘束情報としてそのことを示すのでない限り、 どのレジスタの中に出力が現われるのかを知る方法は、 これ以外にはありません。 出力オペランドは、 asmの後ろにおいて、 どのレジスタの中に出力が現われるのかを明確に示さなければなりません。 =fのような指定は許されません。 オペランド拘束においては、 あるクラスのある特定のレジスタを選択しなければなりません。
  4. 出力オペランドを、 既存のスタック・レジスタの中に挿入することはできません。 387のopcodeでは読み書き兼用のオペランドが使われないため、 すべての出力オペランドの値は、 asm_operandsの前では無効であり、 asm_operandsによってプッシュされます。 レジスタ・スタックのトップ以外の位置にプッシュすることには、 何の意味もありません。 出力オペランドは、 レジスタ・スタックのトップから開始されなければなりません。 出力オペランドが、 レジスタを「スキップ」することはできません。
  5. asm文の中には、 内部の計算処理のために余分のスタック・スペースを必要とするものがあるかもしれません。 これは、 入出力とは無関係なスタック・レジスタの内容を破壊することによって確保されます。

以下に、 合理的な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では、 指定されたハードウェア・レジスタの中に少数の広域変数を置くことができます。 また、 通常のレジスタ変数が割り当てられるべきレジスタを指定することもできます。

広域レジスタ変数の定義

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の機能を使いたい場合に、 これが問題になります。 キーワードasmtypeofinlineは、 `-ansi'を指定してコンパイルされるプログラムの中では問題があるので使うことができません。 その一方で、 キーワードconstvolatilesignedtypeofinlineは、 `-traditional'を指定してコンパイルされるプログラムの中では問題があります。

この問題を解決する方法は、 問題のある個々のキーワードの前後に`__'を付けることです。 例えば、 asmの代わりに__asm__を、 constの代わりに__const__を、 inlineの代わりに__inline__を使ってください。

他のCコンパイラは、 このような代替キーワードを受け付けてくれません。 別のコンパイラでコンパイルを行いたいのであれば、 代替キーワードをマクロとして定義して、 慣習的なキーワードと置き換えることができます。 これは、 以下のようになります。

#ifndef __GNUC__
#define __asm__ asm
#endif

`-pedantic'を指定すると、 多くのGNU C拡張機能に対して警告メッセージが出力されます。 式の前に__extension__と書くことによって、 ある式の中における警告メッセージの出力を防ぐことができます。 __extension__にはこれ以外の作用はありません。

不完全なenum

enumタグを、 それが持つことのできる値を指定せずに定義することができます。 これは、 struct fooと書いてその要素を記述しないようなもので、 不完全な型になります。 後に、 それが持つことのできる値を指定する宣言をすることによって、 その型は完全なものになります。

この型が不完全な間は、 それを使って変数や記憶域を割り当てることはできません。 しかし、 その型へのポインタを操作することはできます。

この拡張機能はあまり役に立たないかもしれませんが、 これによって、 enumの処理とstructunionの処理の一貫性がより高くなります。

この拡張機能は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)
この関数は、 カレントな関数の復帰アドレス、 または、 カレントな関数を呼び出すまでに途中で呼び出されてきた関数の中の1つの復帰アドレスを返します。 引数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は、 これまで説明してきたもの以外にも、 数多くの組み込み関数を提供しています。 その中のいくつかは、 例外や可変個数引数の処理のために内部的に使われるもので、 ときおり変更される可能性があるため、 ここでは説明しません。 これらの関数を一般的に使用することはお勧めしません。

これら以外の関数は、 最適化のために提供されています。

GNU CCには、 標準Cライブラリの多くの関数の組み込み版が含まれています。 これらは、 `-fno-builtin'Cの方言を制御するオプションを参照)オプションが指定された場合でも、 Cのライブラリ関数と同一の意味を持つものとして常に扱われます。 Cのライブラリ関数 allocaffsabsfabsffabsfabsllabsmemcpymemcmpstrcmpstrcpystrlensqrtfsqrtsqrtlsinfsinsinlcosfcoscoslに対応します。

コンパイル時に、 ある値が定数であることが分かっていて、 その値を含む式に対して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
初期のバージョンのC++においては、 アプリケーション定義のメモリ割り当てを実装するために、 thisに対する代入を使用することができました。 現在では、 割り当て関数(`operator new')が、 これと同じ効果を実現するための標準準拠の方法です。
-fexternal-templates
-falt-external-templates
これらは、 テンプレートのインスタンス生成を実装するためにg++が提供している 多くの方法のうちの2つです。 テンプレート・インスタンスの存在箇所を参照してください。 C++標準には、 実装ユニット(implementation unit)間におけるテンプレート定義の構成方法が、 明確に定義されています。 g++は、 標準準拠のコードにとってちょうど適切な、 暗黙の実装メカニズムを持っています。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]