C言語/データ型と変数の高度な話題

ここでは、ISO/IEC 9899:2017(通称 C17)の 6.7 Declarations で定義されている構文と意味を読み解き、データー型と変数への理解を深めようと思います。
N2176 C17 ballot ISO/IEC 9899:2017 §6.7 Declarations:宣言.
記憶域クラス指定子( storage-class specifiers )は6種類あり、いずれもキーワードです。
ISO/IEC 9899:2017(通称 C17)の §6.11.5 Storage-class specifiers(記憶域クラス指定子)を抄訳/引用します。
前方参照:型定義(type definitions; 6.7.8)。
Forward references: type definitions (6.7.8).
typedefとは、データー型に型定義名をつけるためのキーワードである。
typedefを使うことで、マルチプラットホームにおいて可搬性を高めたり (データー型を変更したい場合、typedefの部分を変更するだけで、すべてのデーター型を変更できる。)、 データー型を隠蔽し、カプセル化を向上できる。
externを使うことで、複数のソースファイルで、1つの識別子を共有することができる。
上の例では、myheader.h で外部参照宣言を一括して行い、定義元の file2.c でも myheader.h をインクルードしています。 この事により、宣言と定義で方が食い違っていないことを言語処理系が検査する機会を与えています。
extern を、.h でなく .c で使う時は、宣言と定義の食い違いのリスクを負っていることを意識すべきです。
異なるスコープで宣言された識別子や、同じスコープで2回以上宣言された識別子は、結合(Linkages)と呼ばれるプロセスによって、同じオブジェクトや関数を参照するようにすることができます。
結合には、外部結合( external linkage )、内部結合( internal linkage )、および無結合( none linkage )の3種類があります。
プログラム全体を構成する翻訳単位やライブラリの集合の中では、外部結合された特定の識別子の宣言は、それぞれ同じオブジェクトや関数を意味します。
1つの翻訳単位( translation-unit )の中で、内部結合を持つ識別子の各宣言は、同じオブジェクトまたは関数を表します。無結合な識別子の各宣言は一意の実体を表します。
オブジェクトまたは関数のファイルスコープ識別子の宣言に、記憶域クラス指定子 static が含まれている場合、その識別子は内部リンクを持ちます。
識別子の事前宣言が可視化されているスコープにおいて、記憶域クラス指定子 extern で宣言された識別子について、事前宣言が内部または外部結合を指定している場合、後の宣言における識別子の結合は、事前宣言で指定された結合と同じである。先行宣言が見えない場合、または先行宣言が結合を指定していない場合、その識別子は外部結合を持つ。
関数の識別子の宣言に記憶域クラス指定子がない場合、その結合は、記憶域クラス指定子 extern で宣言された場合と全く同じように決定されます。 オブジェクトの識別子の宣言にファイルスコープがあり、記憶域クラス指定子がない場合、そのリンク先は外部となります。
オブジェクトまたは関数以外と宣言された識別子、関数のパラメータと宣言された識別子、記憶域クラス指定子 extern を持たずに宣言されたオブジェクトのブロックスコープ識別子は、結合されません。
1つの翻訳単位の中で、同じ識別子が内部リンクと外部リンクの両方で現れた場合、その動作は未定義です。
関数宣言に記憶域クラス指定子 static を含めることができるのは、ファイルスコープにある場合のみです。
ファイル有効範囲( file scope )をもつオブジェクト(変数や配列など)や関数を記憶域クラス指定子 static を伴って宣言することで、そのオブジェクトや関数を内部結合とし他の翻訳単位から参照できなくすることができます。 また、ブロック有効範囲のオブジェクトを記憶域クラス指定子 static を伴って宣言することで静的記憶域期間(static storage duration)をもたせることが出来、その生存期間はプログラム実行の全体になります。その値はプログラム開始処理の前に1回だけ初期化します。
autoはふつう省略する。
記憶域クラス指定子autoを伴って宣言されたブロック有効範囲の識別子は、自動記憶域期間をもつ。
registerを使うことで、レジスターを使う。
記憶域クラス指定子registerを用いて宣言されたオブジェクトは、 そのオブジェクトへのアクセスを可能な限り高速にすべきであることを示唆する。 この示唆が効果を持つ程度は、処理系定義とする。 また、registerを伴って宣言されたオブジェクトのどの部分のアドレスも、計算することはできない(記憶域クラス指定子registerを伴って宣言した変数には、アドレス演算子( & )を適用する事はできない)。
変数は一般にメモリー上に確保されるが、メモリーへのアクセスは比較的低速である。 一方、レジスターはCPU内部にあり、レジスターへのアクセスは比較的高速である。
なお、記憶域クラス指定子registerを用いたからといって必ず高速になるとは限らず、 また、用いなくともコンパイラーは自動的に生成コードを最適化するため低速になるとは限らない。
N2176 C17 ballot ISO/IEC 9899:2017 §6.7.2 Type specifiers:型指定子。
C99では、プログラムの移植性を高めるために、いくつかの新しい整数型が定義されています。 すでに利用可能な基本的な整数型では、実際のサイズが実装によって定義され。システムによって異なる可能性があるため、(移植性を確保する上で)不十分であると考えられました。 新しい型は、ハードウェアが通常いくつかの型しかサポートしておらず、そのサポートが環境によって異なる組み込み環境において特に有用です。すべての新しい型は、<inttypes.h> ヘッダー(及び、<stdint.h> ヘッダー)で定義されています。 型は、以下のカテゴリーに分類されます。
以下の表は、型と実装の詳細を取得するためのインターフェースをまとめたものです(nはビット数を意味します)。
列挙体( enumeration )は、名前の付いた整数定数値( integer constant values )の集合で構成されています。それぞれの列挙は、異なる列挙型( enumerated type )を構成します。
ISO/IEC 9899:2017(通称 C17)の §6.7.2.2 Enumeration specifiers(列挙型指定子)を抄訳/引用します。
構文中で、列挙子リスト( enumerator-list )の後ろに , がないケースとあるケースの両方が書かれているので、どちらの書き方も許されます。
この命名ルールは一例ですが、実際のプログラミングでは多くの識別子の名前を考える必要があるので、一貫性のある命名ルールを作ってコードを書くことで可読性・保守性が向上します。
Cのenumには、列挙子を集合で返したり、反復する機能も、列挙子の数を返す機能もないので、ループを廻すときは最初の列挙子から最後の列挙子までインクリメントしながらループすることになります(列挙子に初期値を与えている場合は、この手段も使えません)。
また、C++のコードとしてコンパイルすると、dw++ がエラーになります。
ISO/IEC 9899:2017(通称 C17)の §6.7.2.3 Tags(タグ)を抄訳/引用します。
型修飾子( Type qualifiers )は次のいずれかの組み合わせです.。
const修飾型をもったオブジェクトは初期化以外で変更不可能となります。 つまり、そのオブジェクトを不変値( constant value )として扱うことができ、言語処理系に畳み込みなどの最適化の機会を与えます。 const修飾型をもったオブジェクトは初期化が必須になります。
例、次の宣言のペアは、「不変値への可変ポインター」と「可変値への不変ポインター」の違いを示しています。
ptr_to_constant が指すオブジェクトの内容は、そのポインターを介して変更してはならないが、ptr_to_constant 自体は別のオブジェクトを指すように変更することができる。 同様に、constant_ptr が指すintの内容は変更されても構いませんが、constant_ptr 自体は常に同じ場所を指すものとします。
restrictとは、ポインターに用いる型修飾子であり、そのポインターが指す先を、同一関数・ブロック内の別のポインターが指さないという情報をコンパイラーに伝え、コンパイラーの最適化を促進することができます。 restrict型修飾子を付けても付けなくても、目に見える動作は変わりません。
restrictの例として、標準ライブラリstring.hのmemcpy関数とmemmove関数が挙げられる。
これら2つの関数は、どちらもs2をs1にn文字コピーしますが、memcpyは領域の重なり合わないオブジェクト間でコピーする必要があり、 そのためrestrict型修飾子を用いることができます。
volatileとは、変数がコンパイラーに未知の方法で変更され、又はその他の未知の副作用を持つことをコンパイラーに伝え、コンパイラーの最適化を抑制する型修飾子です。 volatileで型修飾する動機として:
などがあります。 これらのオブジェクトの参照が共通部分式削除や定数畳み込みなどの言語処理系による畳込みでループの最初に一度だけ評価されたり、あるいは全く評価されなくなることを避ける狙いがあります。
ただし、
など、volatileで型修飾する動機になったニーズを完全に充足しているかは検討の余地があります。
原子型指定子( Atomic type specifiers )は、スレッドとともにC11で導入されました。