第2回 オブジェクトの宣言

Cにおけるオブジェクトの宣言も、基本的にはC++と同じです。ただし、微妙に言語仕様が異なる点がありますので、今回はそれらについて解説することにします。

局所オブジェクトの宣言

関数内で局所オブジェクトを宣言する場合は、必ずブロックの先頭で行わなければなりません。すなわち、

int func(void)
{
  int a; /* OK */
  a = 123;
  int b; /* エラー */
  {
    int c; /* OK */
    ...
  }
}

のように、ブロックの中でいったん宣言ではない”文”を記述したあとで、宣言を行うことはできません。しかし、新しい複合文を作れば、そのブロックの先頭で宣言を行うことができます。また、

for (int i = 0; i < 10; i++) /* エラー */
  a[i] = 0;

あるいは、

const char *s = "abc";
while (char c = *s++) /* エラー */
  putchar(c);

のように、if 文、for 文、while 文、switch 文でも、カッコの中で宣言を行うことはできません。

非局所オブジェクトの宣言

関数の外で非局所オブジェクトを宣言する場合は、意外に知られていない相違点があるので注意が必要です。

非局所オブジェクトはデフォルトでは常に外部結合

C++では、const 修飾された非局所オブジェクトは、デフォルトで内部結合になりました。しかし、Cでは、const 修飾の有無にかかわらず、非局所オブジェクトはデフォルトでは常に外部結合になります。

初期化子がない非局所オブジェクトの宣言は仮定義

C++では、extern 記憶クラス指定子を付けた場合をのぞき、初期化子なしで宣言した非局所オブジェクトは常に定義とみなされていました。これは、非局所オブジェクトの宣言の記述位置が翻訳単位内での動的初期化の順序に関わってくるためです。しかし、Cでは動的初期化は行われませんので(これについては後の回に解説します)、前方参照の解決をのぞけば、宣言の記述位置はそれほど重要ではありません。そのため、Cでは、初期化子がない非局所オブジェクトの宣言は仮定義になります。

仮定義は同じ翻訳単位に複数あってもかまいません。コンパイルの際に、同じ翻訳単位内で仮定義された同じ名前のオブジェクトの宣言はひとつにまとめられ、ひとつの実体が作られます。仮定義が複数の翻訳単位にまたがっている場合の動作は未定義ですが、GCCのように、リンク時にひとつの実体にまとめられる場合もあります。

仮定義は可読性が下がるので濫用はさけるべきですが、次のような使い方は積極的に利用すべきかと思います。

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++ではこのような記述をするとコンパイルエラーになってしまいます。そのため、配列をやむなく外部結合にするとか、クラスの静的データメンバにするとか、無名名前空間を使うといった方法で回避しなければなりません。しかし、Cでは上記のような素直な記述が可能になります。