C99におけるオブジェクトの宣言も、基本的にはC++98と同じです。ただし、微妙に言語仕様が異なる点がありますので、今回はそれらについて解説することにします。
局所オブジェクトの宣言
C99では、C++と同じように、ブロックの途中であっても局所オブジェクトの宣言を行うことができます。ただし、C++の宣言は一種の文であったのに対して、C99の宣言は文ではありません。したがって、文が要求されるような文脈に(ブロック無しで)単独で宣言を記述することはできません。具体的には、
0 1 2 3 |
for (i = 0; i < 10; i++) int r = printf("%d\n", i); |
上のような記述は、C++では問題ありませんんがC99ではコンパイルエラーになります。
C99のfor文では、最初の節では宣言を行うことができます。しかし、第2節では宣言を行うことができません。
0 1 2 3 4 5 |
for (int i = 0; /* OK */ int c = str[i]; /* エラー */ i++) ... |
また、
0 1 2 3 4 |
const char *s = "abc"; while (char c = *s++) /* エラー */ putchar(c); |
のように、if 文、while 文、switch 文でも、カッコの中で宣言を行うことはできません。
非局所オブジェクトの宣言
関数の外で非局所オブジェクトを宣言する場合は、意外に知られていない相違点があるので注意が必要です。
非局所オブジェクトはデフォルトでは常に外部結合
C++では、const修飾された非局所オブジェクトは、デフォルトで内部結合になりました。しかし、C99ではconst修飾の有無にかかわらず、非局所オブジェクトはデフォルトでは常に外部結合になります。
初期化子がない非局所オブジェクトの宣言は仮定義
C++では、extern記憶クラス指定子を付けた場合をのぞき、初期化子なしで宣言した非局所オブジェクトは常に定義とみなされていました。これは、非局所オブジェクトの宣言の記述位置が翻訳単位内での動的初期化の順序に関わってくるためです。しかし、C99では動的初期化は行われませんので(これについては後の回に解説します)、前方参照の解決をのぞけば、宣言の記述位置はそれほど重要ではありません。そのため、C99では、初期化子がない非局所オブジェクトの宣言は仮定義になります。
仮定義は同じ翻訳単位に複数あってもかまいません。コンパイルの際に、同じ翻訳単位内で仮定義された同じ名前のオブジェクトの宣言はひとつにまとめられ、ひとつの実体が作られます。仮定義が複数の翻訳単位にまたがっている場合の動作は未定義ですが、GCCのように、リンク時にひとつの実体にまとめられる場合もあります。
仮定義は可読性が下がるので濫用はさけるべきですが、次のような使い方は積極的に利用すべきかと思います。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
static const int a[4]; /* OK */ int func(void) { int i; int result = 0; for (i = 0; a[i] >= 0; i++) result += a[i]; return result; } static const int a[4] = { 1, 2, 3, 4 }; |
比較的大きなテーブルを配列として用意する場合、配列の定義はソースファイルの最後で行い、前方参照を解決するためにソースファイルの最初のほうでは(定義ではなく)宣言だけにしたいことがよくあると思います。しかし、C++ではこのような記述をするとコンパイルエラーになってしまいます。そのため、配列をやむなく外部結合にするとか、クラスの静的データメンバにするとか、無名名前空間を使うといった方法で回避しなければなりません。しかし、C99では上記のような素直な記述が可能になります。
配列・構造体・共用体の初期化子
C99では、配列・構造体・共用体の初期化子に使える構文が拡張されています。
要素指示子
C++(およびCの旧規格)では、集成体の初期化子は、波括弧の中に、最初の要素から順に初期値を並べる必要がありました。しかし、C99では、要素指示子を使うことで、特定要素を指定して初期値を設定することができます。具体的には、次のように記述します。
0 1 2 3 4 |
int array[10] = { [3] = 123, [6] = 456 }; struct A { int a; int b; }; struct A x = { .b = 78, .a = 90 }; |
要素指示子を用いて初期値を指定したあと、要素指示子なしで初期値のみを記述した場合、要素指示子で指定した要素の続きから初期値が設定されることになります。