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

局所オブジェクトの宣言

C11では、C++と同じように、ブロックの途中であっても局所オブジェクトの宣言を行うことができます。ただし、C++の宣言は一種の文であったのに対して、C11の宣言は文ではありません。したがって、文が要求されるような文脈に(ブロック無しで)単独で宣言を記述することはできません。具体的には、

上のような記述は、C++では問題ありませんんがC11ではコンパイルエラーになります。

C11のfor文では、最初の節では宣言を行うことができます。しかし、第2節では宣言を行うことができません。

また、

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

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

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

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

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

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

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

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

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

比較的大きなテーブルを配列として用意する場合、配列の定義はソースファイルの最後で行い、前方参照を解決するためにソースファイルの最初のほうでは(定義ではなく)宣言だけにしたいことがよくあると思います。しかし、C++ではこのような記述をするとコンパイルエラーになってしまいます。そのため、配列をやむなく外部結合にするとか、クラスの静的データメンバにするとか、無名名前空間を使うといった方法で回避しなければなりません。しかし、C11では上記のような素直な記述が可能になります。

配列・構造体・共用体の初期化子

C11では、配列・構造体・共用体の初期化子に使える構文が拡張されています。逆にC++11から導入された一様初期化には対応していません(スカラ型でも一様初期化には対応していません)。

要素指示子

C++(およびCの旧規格)では、集成体の初期化子は、波括弧の中に、最初の要素から順に初期値を並べる必要がありました。しかし、C11では、要素指示子を使うことで、特定要素を指定して初期値を設定することができます。具体的には、次のように記述します。

要素指示子を用いて初期値を指定したあと、要素指示子なしで初期値のみを記述した場合、要素指示子で指定した要素の続きから初期値が設定されることになります。

記憶クラス指定子

auto

C11ではC++のような型推論は使えません。autoは古いC++(C++03まで)と同様の意味で、あくまでも記憶クラス指定子の一種です。

上のように、算術型の初期化子の場合はC11でもコンパイルできますが、型推論が働くわけではなく単にintが省略されているだけの扱いになります。また、算術型以外の初期化子を指定した場合はエラーになります。

register

なお、同じく自動記憶域期間を持つオブジェクトの宣言に使うregister記憶クラス指定子はC++17で廃止されましたが、CではC17になってもまだ以前のように使用することができます。

C++のregisterとは異なり、C言語でregisterを付けて宣言したオブジェクトは&演算子のオペランドにすることができません。

スレッドローカルストレージ

C++11から導入されたthread_localはC11にもあります。ただし、キーワードとしては_Thread_localを使います。<threads.h>をインクルードした場合は_Thread_localに展開されるthread_localマクロが定義されます。

constexpr

C11にはC++のconstexprに相当する機能がありません。C11で定数式に名前を付けたい場合はマクロを使うのが一般的です。整数値の場合は列挙体を使うこともできるでしょう。

境界調整

C++11から導入されたalignasはC11にもあります。ただし、キーワードとしては_Alignasを使います。<stdalign.h>をインクルードした場合は_Alignasに展開されるalignasマクロが定義されます。

オブジェクトの宣言とは違いますが、<stdalign.h>では_Alignofに展開されるalignofマクロも定義されます。


↑ モダンC++プログラマーのためのC11入門