4 宣言

宣言はプログラムに使用する識別子を導入し,その識別子の重要な属性( 型,記憶域クラス,および識別子名など)を指定するために使用します。 また,オブジェクト用に記憶域を確保したり,または関数本体を含む宣言を 定義と呼びます。

一般的な宣言の構文規則については 第4.1節を, 初期化については 第4.2節を,そして外部宣言については 第4.3節を参照してください。

次の種類の識別子を宣言することができます。各宣言と初期化の構文については, 関連する節を参照してくだい。関数については第5章で説明します。


注意
#define命令で作成した前処理マクロは宣言ではありません。 前処理命令でマクロを作成する方法については, 第8章を参照してください。

4.1 宣言の構文規則

宣言の一般的な構文は,次のとおりです。

宣言:


      宣言指定子       初期宣言子並び(opt) 
        ;

宣言指定子:


      記憶域クラス指定子       宣言指定子(opt)
      型指定子       宣言指定子(opt)
      型修飾子       宣言指定子(opt)

初期宣言子並び:


      初期宣言子
      初期宣言子並び     初期宣言子

初期宣言子:


      宣言子
      宣言子    =     初期化子

次は,宣言の一般的な構文に関する注意事項です。

次にその例を示します。

     volatile static int data = 10;

この宣言は,修飾型(型修飾子を持つデータ型。この例では volatile int を修飾している。),記憶域クラス(static ),宣言子( data ),および初期値(10 )を示しています。 また,記憶域がデータ・オブジェクトdata用に確保されるため, この宣言は定義であるともいえます。

これは単純な例ですが,複雑な宣言の場合は解釈するのがより難しくなります。 Compaq C宣言の解釈については,プラットフォームに固有の Compaq Cのマニュアルを参照してください。

次は宣言に適用される意味規則です。

記憶域割付け

記憶域は,次の環境でデータ・オブジェクトに割り付けられます。


注意
コンパイラは,必ずしもソース・ コードでの宣言順序に従って,個別の変数をメモリ位置に割り当てるとはかぎりません。 さらに,ソース・コード,コマンド行オプションに対する表面的には関係のない変更, あるいはコンパイラのバージョンが1つ新しくなったことにより, 割り当ての順序が変わる可能性があります。これは, 本質的に予測不可能です。変数の相互の位置を制御するための唯一の方法は, それらの変数を同一struct型のメンバとすることです。 または,OpenVMS Alphaシステムでは, #pragma extern_model strict_refdefnoreorder属性を使用します。

4.2 初期化

初期化子は,オブジェクトに初期値を提供します。構文は次のとおりです。

初期化子:

    代入式
    { 初期化子並び }
    { 初期化子並び,}

初期化子並び:

    ディジグネーション(opt) 初期化子
    初期化子並び,ディジグネーション(opt) 初期化子

ディジグネーション:

    指名子並び =

指名子並び:

    指名子
    指名子並び 指名子

指名子:

    [ 定数式 ]
    . 識別子

各型のオブジェクトの初期化については,これ以降の各節で説明します。 ただし,次の規則はCompaq Cにおけるすべての初期化に適用されます。

Compaq Cでは,慣習として初期化子を余分な中括弧で囲むことを許可しています。 たとえば,書式をわかりやすくするために中括弧で囲みます。 このような初期化子は,使用するパーサの型によって解析が異なります。 Compaq CではANSI規格によって指定されている解析方法を使用しており, これはトップダウン解析として知られています。部分的に中括弧で囲んだ初期化子をボトムアップ解析を採用したプログラムで使用した場合, その結果は保証できません。コモンC互換性モードで不要な中括弧を検出した場合や, コマンド行でエラーを検査するコンパイル・オプションが指定された場合には, コンパイラは警告メッセージを出します。

4.3 外部宣言

関数の外にあるオブジェクト宣言を外部宣言と呼びます。外部宣言は 内部宣言とは対照的なものです。内部宣言は関数またはブロック内で作成され, その関数またはブロックの内部に存在し,その内部でのみ参照できます。 コンパイラは内部宣言された識別子を宣言した場所からそのブロックの終わりまで認識します。

オブジェクトの宣言にファイル・スコープと初期値がある場合には,その宣言はオブジェクトの 外部宣言でもあります。C言語プログラムは, オブジェクトと関数の一連の外部定義から構成されています。

定義は,宣言する要素用に記憶域を確保します。次にその例を示します。

     float fvalue = 15.0;                /* external definition  */
     main ()
     {
       int ivalue = 15;                  /* internal definition  */
     }

外部データ宣言と外部関数定義は,データ宣言または関数宣言と同じ形式で指定します( 標準関数宣言の構文については第5章を参照してください)。 適用される規則は次のとおりです。


注意
Compaq Cでは事前に宣言しておかなくても外部関数を呼び出すことはできますが, 推奨できる方法ではありません。 これでは型検査が行われないため,バグが発生しやすくなります。 このような関数呼出しが行われると,コンパイラは int型の外部宣言がその呼出しを含むブロックに指定されている場合と同様に関数を処理します。 次にその例を示します。
     void function1()
     {
     int a,b;

     x (a,b)
     }
この場合,コンパイラは extern int x(); 宣言が function1定義ブロック内に指定されているかのように処理します。

コンパイル単位の識別子の最初の宣言では明示的に,または staticキーワードを省略することでそれが内部識別子か外部識別子かを指定しなければなりません。 オブジェクトごとに1つずつしか定義を使用できません。 同じオブジェクトに対して矛盾する定義または重複する定義を行わない限り, 同じオブジェクトに複数の宣言を行うことができます。

外部オブジェクトは,明示的な初期化または仮定義により定義することが可能です。 ファイル・スコープを持ち,初期化子を持たず, static以外の記憶域クラス指定子を持つオブジェクトの宣言は仮定義です。 オブジェクトの完全定義が見つからない場合には, コンパイラは仮定義をオブジェクトの唯一の定義とみなします。すべての宣言についてオブジェクトが定義されるまでは, 実際に記憶域が割り付けられることはありません。

コンパイル単位内の1つのオブジェクトに複数の仮定義があり,そのオブジェクトの外部定義がない場合には, コンパイラは0の初期化子を持つオブジェクト( コンパイル単位の終わりまで合成型を持つオブジェクト)のファイル・ スコープ宣言があるものとみなします。合成型の定義については, 第2.7節を参照してください。

オブジェクトの宣言が仮定義であり内部結合を持つ場合に宣言される型は, 不完全型であってはなりません。仮定義の例については,第2.9節を参照してください。

4.4 単純オブジェクトの宣言

単純オブジェクトとは,基底データ型の1つを持つオブジェクトです。したがって, 単純オブジェクトは汎整数型または浮動小数点型を持つことができます。 すべてのオブジェクトと同様に,単純オブジェクトは名前の付けられた記憶位置であり, その値はプログラムの実行中に変更できます。 プログラムで使用する単純オブジェクトは,すべて宣言する必要があります。

単純オブジェクト宣言は以下の要素から構成されます。

4.4.1 単純オブジェクトの初期化

単純オブジェクトの初期化子は,等号( = )とその後に続く単式から構成されます。 次にその例を示します。

     int x = 10;
     float y = ((12 - 2) + 25);

この場合,宣言は xオブジェクトを整数値として宣言し, 最初は10に等しいものとして定義しています。また, yを浮動小数点値として宣言し,初期値35を持つものとして定義しています。

初期化子がない場合の autoオブジェクトの初期値は定義されていません。 明示的な初期化が行われない staticオブジェクトは,自動的に0に初期化されます。オブジェクトが static配列または構造体である場合には, すべてのメンバが0に初期化されます。

外部結合または内部結合を持つブロック・スコープの識別子( extern または static キーワードを使用して宣言されている識別子)には,その宣言に初期化子を含めることはできません。 これは,その識別子が別の場所で初期化されるからです。

4.4.2 整数オブジェクトの宣言

整数オブジェクトはintlongshortsigned, および unsigned の各キーワードで宣言できます。 char も使用できますが,小さい値にのみ使用できます。 次に,整数宣言の文の例を示します。

     int x;        /*  Declares an integer variable x    */
     int y = 10;   /*  Declares an integer variable y    */
                   /*  and sets y's initial value to 10  */

使用可能な値の範囲を示すために,いくつかのキーワードを一緒に使用できます。 次にその例を示します。

     unsigned long int a;
     signed long;           /*  Synonymous with "signed long int"   */
     unsigned int;

オブジェクトに汎整数データ型を選択する場合には,整数オブジェクトを表現できるようにその値の範囲を考慮しなければなりません。 汎整数データ型のサイズと範囲については, 第3章を参照してください。

4.4.3 文字変数の宣言

文字オブジェクトは,charキーワードで宣言します。次の例は, 文字オブジェクトの初期化を行う文字宣言を示しています。

     char ch = 'a'; /* Declares an object ch with an initial value 'a' */

Compaq Cでは,文字列リテラルは char型の配列に格納されます。 配列についての詳細は,第4.7節を参照してください。

4.4.4 浮動小数点変数の宣言

浮動小数点オブジェクトを宣言する場合は,格納されるオブジェクトに必要な精度を決定する必要があります。 単精度または倍精度オブジェクトを使用できます。 単精度の場合は floatキーワードを使用してください。 倍精度の場合は double または long doubleキーワードを使用してください。 次にその例を示します。

     float x = 7.5;
     double y = 3.141596;

浮動小数点型の範囲および精度については,プラットフォームに固有の Compaq Cのマニュアルを参照してください。

4.5 列挙型の宣言

列挙型はユーザ定義の整数型であり,列挙定数を定義するものです。 この列挙定数とは,整数として表現できる値を持つ整数定数式のことです。 列挙型の宣言の構文は次のとおりです。

列挙型指定子:

      enum  識別子(opt)   { 列挙子並び  }
      enum  識別子(opt)   { 列挙子並び , }
      enum  識別子

列挙子並び:

      列挙子
      列挙子並び列挙子

列挙子:

      列挙定数
      列挙定数  =  定数式

Compaq Cでは,enum型のオブジェクトは互換性があります。

次に,列挙型と列挙型タグの宣言例を示します。

     enum shades
        {
           off, verydim, dim, prettybright, bright
        }  light;

この宣言は,shades列挙型の light変数を定義しています。light は任意の列挙型値として扱うことができます。

shadesタグは新しい型の列挙型タグです。 off から bright は0から4の値を持つ列挙定数です。この列挙定数は定数値であり, 整数定数が有効な場合にはいつでも使用できます。

いったんタグが宣言されると,次に示す宣言のようにその列挙型の参照として使用できます。 ここでは,light1変数は shades列挙データ型のオブジェクトになります。

     enum  shades  light1;

列挙型に対して不完全型宣言を行うことはできません。次にその例を示します。

     enum e;

enumタグは,他の名前空間にある同じプログラム中の他の識別子と同じ名前でも構いません。 ただし,enum定数名は変数および関数と同じ名前空間を共有するので, 不明瞭にならないように固有の名前を付ける必要があります。

内部的には,各列挙定数は1つの整数定数と関連しています。省略時の設定では, コンパイラは最初の列挙定数に0を割り付け,後に続く列挙定数には1 ずつ増加した値を割り付けます。それぞれの列挙定数には任意の整数定数値を設定できます。 このような構成要素の後に続く列挙定数は(特定値に設定されていない限り) ,前の値よりも大きい値を受け取ります。 次にその例を示します。

     enum spectrum
        {
           red, yellow = 4, green, blue, indigo, violet
        }  color2 = yellow;

この宣言では,redyellowgreenblue, . . . にそれぞれ値0,4,5,6, . . . を割り付けます。列挙定数に重複する値を代入することも許可されています。

color2 の値は整数(4)であり," red " または "yellow " のような文字列ではありません。

4.6 ポインタの宣言

ポインタは,オブジェクトまたは関数のメモリ・アドレスを含んでいる変数です。 ポインタ変数は,アスタリスク記号およびそのポインタが示すオブジェクトのデータ型を使用することにより, ポインタ型として宣言されます。 構文は次のとおりです。

ポインタ:

      * 型修飾子並び(opt)
      * 型修飾子並び(opt)        ポインタ

型修飾子並び:


      型修飾子
      型修飾子並び       型修飾子

省略時の設定では,Compaq Cのポインタのビット長は, OpenVMSシステムで使用する場合には32ビットであり, Tru64 UNIXシステムで使用する場合には64ビットです。 省略時の設定が異なっていても,OpenVMS AlphaTru64 UNIXの両方のシステムで32ビット(short)と64ビット(long)のポインタをサポートしています。 Compaq Cでは,ポインタのサイズを制御するための修飾子/ スイッチや#pragma前処理命令が用意されています。

型修飾子は const volatile ,__unaligned (Alpha)または__restrictのいずれか, あるいはそれを組み合わせたものです。

ポインタ型のオブジェクトの宣言は,次の例のとおりです。

     char *px;

この例では,px識別子を char型のオブジェクトへのポインタとして宣言しています。 この例では,型修飾子は使用していません。 式 *px は,px が示す char を導きます。

次に示す宣言は,定数への変数ポインタ,変数への定数ポインタ,および定数オブジェクトへの定数ポインタ間の相違を示しています。

     const int *ptr_to_constant;     /*  pointer variable pointing
                                         to a const object         */
     int *const constant_ptr;        /*  constant pointer to a
                                         non-const object          */
     const int *const constant_ptr;  /*  Const pointer to a
                                         const object              */

ptr_to_constant が示すオブジェクトの定数はこのポインタで変更することはできませんが, ptr_ to_constant自体は別の const修飾オブジェクトを示すように変更することができます。 これに対して, constant_ptr が示す整数の定数は変更できますが, constant_ptr自体は常に同じ位置を示します。

constant_ptr定数ポインタの宣言は,int へのポインタ型の定義を含めることにより明確にできます。 次の例では,constant_ptr int への const 修飾ポインタ 型を持つオブジェクトとして宣言しています。ポインタの値( アドレス)は定数です。

     typedef int *int_ptr;
     const int_ptr constant_ptr;

__unalignedデータ型修飾子は,Alphaシステム上でポインタ定義に使用できます。 この修飾子は,ポイントしているデータが正しいアドレスで適切に境界調整されていないことをコンパイラに指示します。 適切に境界調整するには, オブジェクトのアドレスはその型のサイズの倍数でなければなりません。 たとえば,2バイトのオブジェクトは偶数のアドレスで境界調整する必要があります (Alpha)

__unalignedと宣言したポインタでデータをアクセスすると,コンパイラはデータをコピーまたは格納するために必要な追加のコードを生成して, 境界調整のエラーが出ないようにします。境界調整の誤ったデータは一切使用しないのが良いのですが, パックされた構造体にアクセスする必要やその他の理由から, 使用される場合があります (Alpha)

__restrict 型修飾子は,ポインタが明確なオブジェクトを指していることを示すために使用され, コンパイラによる最適化が行われるようにします( 第3.7.4項を参照) 。

extern または staticポインタ変数は,明示的に初期化されない限り空ポインタに初期化されます。 空ポインタとは,値が0のポインタです。 初期化されていない場合の autoポインタの定数は定義されていません。

4.6.1 voidポインタの宣言

voidポインタは指定されたデータ型を持たないポインタであり, そのポインタが示すオブジェクトを記述します。実際にはこれが汎用ポインタと呼ばれます。ANSI C 規格が制定される前は,汎用ポインタを定義するために char * が使用されていました。 ただし,この方法は移植性が低いため,ANSI規格では現在推奨していません。

任意の型へのポインタはキャストせずに voidポインタに代入することが可能であり, また,その逆も可能です。キャスト演算子については, 第6.4.6項を参照してください。 次の文では,明示的にキャストすることなく voidポインタを他の型のポインタに代入する方法を示しています。

     float *float_pointer;
     void  *void_pointer;
        .
        .
        .
     float_pointer = void_pointer;
                                             /*    or,     */
     void_pointer  = float_pointer;

仮引数値または返却値が型の不明なポインタである場合, voidポインタは関数呼出し,関数の実引数,または関数プロトタイプで使用されます。 次の例では,voidポインタが汎用返却値として使用されています。

     void *memcpy (void *s1, const void *s2, size_t n);
     {
        void  *generic_pointer;
        .
        .
        .
     /* The function return value can be a pointer to many types. */

     generic_pointer = func_returning_pointer( arg1, arg2, arg3 );
        .
        .
        .
     /*  size_t is a defined type                                 */
     }

関数宣言に voidを使用する方法については,第5.3節を参照してください。

4.6.2 ポインタの初期化

ポインタ・オブジェクトは単式で初期化できます。次にその例を示します。

     int i = 10;
     int *p = &i;  /*  p is a pointer to int, initialized */
                   /*  as holding the address of i        */

初期化子がない場合には,static および externポインタの値は自動的に空ポインタ(メモリ位置0 へのポインタ)に初期化されます。

次に示す宣言は char へのポインタ型で p を定義し,p を初期化して,長さ4の char型配列のオブジェクトを示すようにします。このオブジェクトの各要素は, 文字列リテラルによって初期化されます(ヌル文字は配列の4 番目のメンバです)。配列の定数を修正するために p を使用した場合の動作結果は定義されていません。

     char *p =  "abc";

4.7 配列の宣言

次に示す構文のように,配列は大括弧 [] で宣言します。


      記憶域クラス指定子(opt) 型指定子  宣言子 [* または 定数式並び(opt)]

次の例は,10個の整数の要素を持つ配列の宣言を示します。table_ one という変数を使用しています。

     int table_one[10];

型指定子は要素のデータ型を示しています。配列の各要素は,任意のスカラ・ データ型または集合体データ型の要素になります。table_ one識別子は配列名を指定します。定数式の10は,1 次元での要素数になります。C言語での配列は0が基底です。つまり,次の例に示すように配列の最初の要素は0 の添字で識別されます。

     int x[5];
     x[0] = 25; /* The first array element is assigned the value 25 */

宣言中の大括弧で囲んだ式は, 区切り子(*),または 0 よりも大きい値を持つ整数定数式でなければなりません。

*を大括弧で囲んで指定すると, 配列の型はサイズを指定しない可変長配列型になります。 この型は,関数プロトタイプ・スコープの宣言でのみ使用できます。

サイズの式が整数定数式で,要素の型が既知の定数サイズの場合, 配列型は可変長配列型にはなりません。 それ以外の場合は,可変長配列型になります。 可変長配列型の各インスタンスのサイズは,その存在期間の間変更されません。 可変長配列についての詳細は, 第4.7.3項を参照してください。

*または定数式を省略すると, 不完全な配列宣言が作成されます。 これは,次の場合に役に立ちます。

また,配列のメンバはポインタでも構いません。次の例は,浮動小数点数の配列と浮動小数点数のポインタの配列を宣言する例です。

     float fa[11], *afp[17];

関数の仮引数を配列として宣言すると,コンパイラはその宣言を配列の最初の要素へのポインタとみなします。 たとえば,x が仮引数であり,整数の配列を表すことを目的としている場合には, 次に示す宣言のいずれかで宣言することができます。

     int x[];
     int *x;
     int x[10];

関数の仮引数の場合には,配列に指定するサイズはどのような長さでも構わないことに注意してください。 これは,ポインタが常に配列の最初の要素のみを示すためです。

C言語は,配列の配列として宣言された配列をサポートしています。これを多次元配列と呼びます。 次の例では,table_one変数は20 の整数を含む2次元配列になっています。

     int table_one[10][2];

配列は行優先順序で格納されます。この例の場合, table_one[0][0]要素の直後に table_one[0][1]が続き,その直後に table_one[1][0]が続きます。

4.7.1 配列の初期化

配列は,中括弧で囲んだ定数式並びによって初期化されます。不完全配列宣言の初期化子並びは配列型を完了し, 配列のサイズを完全に定義します。 したがって,不明のサイズの配列を初期化すると,初期化子並びの初期値の数が配列のサイズを決定します。 たとえば,次に示す宣言は,3つの要素の配列を初期化します。

     int x[] = { 1, 2, 3 };

初期化する配列が static記憶域クラスである場合には, 初期化子は定数式でなければなりません。

サイズを指定した配列の初期化子は,1対1でそれぞれの配列メンバに代入されます。 メンバに対して初期化子が少ない場合には,残りのメンバは0 に初期化されます。指定したサイズ配列に対して多すぎる初期化子を表示すると, エラーになります。次にその例を示します。

     int x[5] = { 0, 1, 2, 3, 4, 5 };    /*  error    */

文字列リテラルは,char または wchar_t配列に代入される場合があります。この場合,文字列の各文字は1 次元配列の1つのメンバを表しており,その配列はヌル文字で終了します。 配列を文字列リテラルへのポインタによって初期化した場合は, その文字列リテラルをそのポインタで変更することはできません。

文字列リテラルで配列を初期化する場合,初期化する文字列を二重引用符で囲みます。 次にその例を示します。

     char string[26] = { "This is a string literal." };
                       /* The braces above are optional here */

終了ヌル文字はサイズに余裕があれば,この例に示したように文字列の終わりに追加されます。 文字を含む配列を初期化するための別の形式は,次のとおりです。

     char string[12] = {'T', 'h', 'i', 's', ' ', 'w', 'a', 'y' };

この例では,文字列の値 "This way " を含む1次元の配列を作成しています。この配列の文字は自由に変更できます。 初期化されない残りの配列メンバは,自動的に0に初期化されます。

文字列リテラルに使用する配列のサイズを明示的に記述していない場合には, そのサイズは文字列の文字数(終了ヌル文字を含む)によって決まります。 配列のサイズを明示的に記述している場合には,その配列よりも長い文字列リテラルで配列を初期化するとエラーになります。


注意
ヌル文字は,自動的に配列に追加されない場合があります。 つまり,配列サイズを明示的に指定した場合に, 初期化子の数で配列サイズが満杯になった場合です。次にその例を示します。
     char c[4] = "abcd";
この場合,c配列は4つの指定文字であるa,b,c,dのみを保持します。 したがって,ヌル文字でこの配列が終了することはありません。

多次元配列のメンバを初期化する場合には,次に示す規則に従って中括弧を省略できます。

次にその例を示します。

     float x[4][2] = {
             { 1, 2 }
             { 3, 4 }
             { 5, 6 }
     };

この例では,1と2が x配列の最初の行を初期化し, それに続く2つの行が2番目と3番目の行をそれぞれ初期化します。この初期化は,4 番目の行が初期化される前に終わるので,4番目の行のメンバは省略時の設定である0 になります。結果は次のとおりです。

     x[0][0] = 1;
     x[0][1] = 2;
     x[1][0] = 3;
     x[1][1] = 4;
     x[2][0] = 5;
     x[2][1] = 6;
     x[3][0] = 0;
     x[3][1] = 0;

次に示す宣言でも,前の例と同じ結果になります。

     float x[4][2] = { 1, 2, 3, 4, 5, 6 };

この場合,コンパイラは配列を1行ずつ使用可能な初期値で満たします。 コンパイラは,1と2を最初の行(x[0] )に,3と4を2番目の行( x[1] )に,5と6を3番目の行(x[2] )に入れます。 配列の残りのメンバは0で初期化されます。


注意

4.7.2 ポインタと配列

配列のデータ・オブジェクトは,配列添字を使用する代わりにポインタで参照できます。 このポインタのデータ型は,「配列へのポインタ」 として参照されます。配列名自体はポインタと同様に機能するため, 配列要素へのアクセスにはいくつかの代替方法があります。 次にその例を示します。

     int x[5] = { 0, 1, 2, 3, 4 }; /* Array x declared with five elements       */
     int *p = x;                   /* Pointer declared and initialized to point */
                                   /* to the first element of the array x       */
     int a, b;
     a = *(x + 3);                 /* Pointer x incremented by twelve bytes     */
                                   /* to reference element 3 of x               */
     b = x[3];                     /* b now holds the same value as a           */

この例では,a は逆参照演算子( * )を使用して3を受け取ります。b は添字演算子を使用して, 同じ値を受け取ります。それぞれの単項演算子についての詳細は, 第6章を参照してください。

a の代入は,x へのポインタが増分された結果であることに注意してください。 これをスケーリングといい, ポインタ演算のすべての型に適用されます。スケーリングでは, 配列メンバのメモリ・アドレスの計算時にコンパイラが配列要素のサイズを考慮します。 たとえば,x配列の各メンバが4 バイト長の場合に初期ポインタ値に3を加算すると,この3に配列メンバのサイズ( この場合は4)を乗算した値に自動的に変換されます。これによって, z = *(y + 3);というポインタ算術を理解しやすく表現することができます。

関数の実引数として配列を渡す場合には,配列の最初の要素へのポインタのみが呼び出される関数に渡されます。 配列型からポインタ型への変換は, 暗黙に行われます。配列名が配列の最初の要素へのポインタに変換された後は, 他のポインタと同様にポインタを増分,減分,または逆参照してその配列内のデータを処理できます。 次にその例を示します。

     int func(int *x, int *y) /* The arrays are converted to pointers         */
     {
        *y = *(x + 4);        /* Various elements of the arrays are accessed  */
     }

ポインタはアドレスを保持するだけの大きさです。すなわち,配列へのポインタは, その配列の要素のアドレスのみを保持します。配列自体は配列のメンバすべてを保持するのに十分な大きさです。

sizeof演算子を配列に適用すると,配列の最初の要素のサイズではなく, 配列全体のサイズを返します。

4.7.3 可変長配列

可変長配列を使用すると, auto記憶域クラスの配列オブジェクトと, ブロック・スコープで宣言されているtypedef配列は, 実行時に計算される式を境界とすることができます。

また,可変長配列を使用すると,他の仮引数で次元が指定される配列 (FORTRANのassumed-shape配列のようなもの) を仮引数に持つ関数の宣言と定義ができます。

次の例では,その両方の使用方法を示します。 sub関数の定義でプロトタイプ構文が使用されていることと, 次元仮引数が,それを使用する配列仮引数より前にあることに注意してください。 次元仮引数の後に,それを使用する配列仮引数が続く関数を定義する場合は, KernighanとRitchieのCの構文を使用して関数定義を記述しなければなりません (この構文では, 仮引数の型の宣言を仮引数自体とは異なる順序で記述できるためです)。 このような関数定義は,一般的には避けてください。

  #include <stdio.h>
  #include <stdlib.h>
 
  void sub(int, int, int[*][*]); 
 
  int main(int argc, char **argv) 
  { 
      if (argc != 3) { 
          printf("Specify two array bound arguments.\n"); 
          exit(EXIT_FAILURE); 
      } 
      { 
          int dim1 = atoi(argv[1]); 
          int dim2 = atoi(argv[2]); 
          int a[dim1][dim2]; 
          int i, j, k = 0; 
          for (i = 0; i < dim1; i++) { 
              for (j = 0; j < dim2; j++) { 
                  a[i][j] = k++; 
              } 
          } 
          printf("dim1 = %d, dim2 = %d.", 
                 sizeof(a)/sizeof(a[0]), 
                 sizeof(a[0])/sizeof(int)); 
 
          sub(dim1, dim2, a); 
          sub(dim2, dim1, a); 
      } 
      exit(EXIT_SUCCESS); 
  } 
 
  void sub(int sub1, int sub2, int suba[sub1][sub2]) 
  { 
      int i, j, k = 0; 
      printf("\nIn sub, sub1 = %d, sub2 = %d.", 
                        sub1,      sub2); 
      for (i = 0; i < sub1; i++) { 
          printf("\n"); 
          for (j = 0; j < sub2; j++) { 
              printf("%4d", suba[i][j]); 
          } 
      } 
  } 

OpenVMSシステムでは, 非標準のalloca intrinsic,__ALLOCA の代わりに可変長配列を使用することがよくあります。

ただし,__ALLOCAと可変長配列には重要な違いがあります。 __ALLOCAによって割り当てられた記憶域は 関数から戻るまで解放されませんが,可変長配列によって割り当てられた記憶域は, それを割り当てたブロックを終了した時点で解放されるという点です。 __ALLOCAが,可変長配列宣言のスコープ内部で呼び出された場合 (可変長配列の宣言を含むブロックの内部でネストされているブロックも含む), __ALLOCAへの呼出しによって割り当てられた記憶域は, その可変長配列の記憶域が解放された時点で解放されます (つまり,関数から戻る時点ではなく,ブロックの終了時に解放されます)。 このような場合,コンパイラは警告を出力します。

4.8 構造体と共用体の宣言

構造体は一連のメンバで構成されており,その各メンバには記憶域が順に割り付けられます。 これに対して,共用体も一連のメンバで構成されていますが, その各メンバの記憶域は重複しています。 構造体と共用体の宣言は,次に示すように同じ形式です。

構造体または共用体指定子:

      struct または union 識別子(opt)  { 構造体宣言並び }
      struct または union 識別子

struct または union:

      struct
      union

構造体宣言並び:


      構造体宣言
      構造体宣言並び       構造体宣言

構造体宣言:


      指定子修飾子並び     構造体宣言子並び;

指定子修飾子並び:


      型指定子       指定子修飾子並び(opt)
      型修飾子       指定子修飾子並び(opt)

構造体宣言子並び:


      構造体宣言子
      構造体宣言子並び   構造体宣言子

構造体宣言子:


      宣言子
      宣言子(opt)  :  定数式

構造体および共用体は,関数型または不完全型を持つことはできません。 構造体および共用体はそれ自体のインスタンスをメンバとして追加することはできませんが, それ自体のインスタンスへのポインタを追加することはできます。 メンバを持たない構造体の宣言であれば受け入れ可能であり, そのサイズは0です。

各構造体または共用体は,コンパイル単位内に固有の構造体型または共用体型を作成します。 struct または unionキーワードをタグの前に付けることができます。 enumタグが列挙型に名前を与えるのと同様に,このタグは構造体型または共用体型に名前を与えます。 このタグを struct または unionキーワードと一緒に使用すれば, 長い定義を繰り返さずにその型の変数を宣言できます。

タグの後には,メンバ宣言並びを囲む中括弧 { } を続けます。 並びの各宣言にはデータ型と1つ以上のメンバ名を指定します。 構造体または共用体メンバの名前は,他の変数,関数名,他の構造体メンバ, または共用体のメンバと同じにすることができます。コンパイラは, 使用状況に応じてこれらを区別します。メンバ名のスコープは,使用している構造体または共用体のスコープと同じです。 中括弧を閉じて並びを完了すると, 構造体型または共用体型は完了します。

構造体タグまたは共用体タグに使用する識別子は,そのスコープ内で参照できるタグの中で固有でなければなりません。 ただし,タグ識別子は,変数名または関数名に使用する識別子と同じにすることができます。 また, タグはメンバ名と同じ名前にすることもできます。コンパイラは,それらを名前空間と使用状況で区別します。 タグのスコープは,使用している宣言のスコープと同じです。

構造体と共用体には,他の構造体と共用体を含めることができます。次にその例を示します。

     struct person
        {
           char first[20];
           char middle[3];
           char last[30];
           struct         /*  Nested structure here  */
           {
               int day;
               int month;
               int year;
           } birth_date;
        }  employees, managers;

構造体宣言または共用体宣言は,次に示す形式のいずれかにすることができます。

4.8.1 構造体と共用体の類似点

構造体と共用体には,次のような共通の特性があります。

4.8.2 構造体と共用体の相違点

構造体と共用体の相違点は,メンバの格納および初期化の方法にあります。 次に相違点について説明します。

4.8.3 ビット・フィールド

構造体の利点の1つは,構造体にビット単位でデータをパックできることです。

構造体メンバは通常,基本型サイズを持つオブジェクトです。ただし,指定したビット数のみで構成される構造体メンバを宣言することもできます。 このようなメンバをビット・フィールドと呼びます。次の構文に示すように, ビット・フィールドの長さ,つまり負以外の汎整数定数式はコロンでフィールド名と区切られます。

構造体宣言子:


      宣言子 : 定数式
      : 定数式

ビット・フィールドによって,構造体の記憶域割付けの制御が向上し,メモリ内に情報をより緊密にパックすることができます。 また,データを記憶域に高密度でパックすることができます。

ビット・フィールド型は,名前のないビット・フィールドの場合以外は指定する必要があります。 また,ビット・フィールド型は intunsigned int,または signed int型を持つことができます。ビット・フィールド値は, 宣言したサイズのオブジェクトに格納できるだけの小さい値にする必要があります。

コンパイラの省略時のモードでは, enumlongshort, および char型もビット・フィールドに使用できます。

ビット・フィールドには名前を付けても付けなくても構いません。宣言子を持たないビット・ フィールド宣言(例: :10 )は,名前のないビット・ フィールドを示します。これは,指定のレイアウトに合わせるために, 構造体をパディングするときに有効です。ビット・フィールドに幅0 を割り付けると,その境界調整単位にはそれ以上ビット・フィールドが置かれないことを示し, 宣言子を命名することはできません。メンバの宣言子( 存在する場合)とフィールド幅をビット単位で示す定数式とを区切るには, コロン : を使用します。フィールドは, 32ビット(1ロングワード)以下でなければなりません。

非ビット・フィールドの構造体メンバはバイト境界で調整されるため,名前のない形式は構造体の記憶域に名前のない空きをつくることができます。 特殊な場合として,幅0の名前のないフィールドの次のメンバ(通常は別のフィールド) は,バイト境界で境界調整されます。つまり,幅が0のビット・ フィールドの構造体メンバは,それ以上ビット・フィールドを境界調整単位にパックしてはならないということになります。

ビット・フィールドを使用する際に適用される規則は,次のとおりです。

ビット・フィールドの列はできるだけ緊密にパックされます。DEC Cではビット・フィールドは右から左に,つまり下位ビットから上位ビットに割り付けられます。

ビット・フィールドを作成するには,構造体メンバとして識別子,コロン, および識別子幅(ビット単位)を指定します。次の例では,3つのビット・ フィールドが構造体宣言に作成されます。

     struct {
      unsigned int a : 1;  /*  Named bit field (a)    */
      unsigned int   : 0;  /*  Unnamed bit field = 0  */
      unsigned int   : 1;  /*  Unnamed bit field      */
     }  class;

最初と3番目のビット・フィールドは1ビット幅であり,2番目のビットは0 ビット幅です。これによって,次のメンバはバイト境界上に境界調整されます。

他のビット・フィールドの直後に宣言されなかったビット・フィールド( 長さ0のビット・フィールドを含む)は,それらの型によって強制される境界調整条件を持ちます。 この条件は,int の境界調整条件よりは必ず大きくなります。 他のビット・フィールドの直後に続くビット・ フィールドの宣言では,領域が十分に残っていれば同じ境界調整単位の隣接する領域にそのビットがパックされます。 これ以外の場合はパディングが挿入され,2 番目のビット・フィールドは次の境界調整単位に置かれます。

構造体内のビット・フィールド境界調整の情報に関するプラットフォーム固有の情報については, 該当するCompaq Cのマニュアルを参照してください。

4.8.4 構造体の初期化

すべての構造体は,構成要素の初期値を中括弧で囲んだ並びで初期化できます。 また,自動記憶域クラスを持つ構造体は,互換型の式で初期化することもできます。

初期化子は1対1で構成要素に代入されます。構造体のメンバよりも初期化子が少ない場合には, 残りのメンバは0に初期化されます。構成要素の数に対して構造体に表示する初期化子が多すぎると, エラーが生じます。名前のない構造体または共用体のメンバはすべて, 初期化時には無視されます。

コンマで初期値を区切り,中括弧 { } で範囲を決定します。 次の例では,それぞれ2つのメンバを持つ2つの構造体を初期化しています。

     struct
        {
           int i;
           float c;
        }  a = { 1, 3.0e10 },  b = { 2, 1.5e5 };

コンパイラは,各メンバの増加順に構造体の初期化子を代入します。構造体の中央にあるメンバは, それ以前のメンバを初期化せずに初期化することはできません。 例 4-1は,構造体の配列に適用される初期化規則の例を示しています。

例 4-1 構造体の初期化の規則

#include <stdio.h>

main()
{
   int m, n;
   static struct
      {
         char ch;
         int i;
         float c;
      }  ar[2][3] =
          {                         【1】
             {                      【2】
                { 'a', 1, 3e10 },   【3】
               { 'b', 2, 4e10 },
               { 'c', 3, 5e10 },
            }
         };

   printf("row/col\t ch\t i\t      c\n");
   printf("-------------------------------------\n");
   for (n = 0; n < 2; n++)
      for (m = 0; m < 3; m++)
         {
            printf("[%d][%d]:", n, m);
            printf("\t %c\t %d\t %e\n",
                   ar[n][m].ch, ar[n][m].i, ar[n][m].c);
         }
}

例 4-1の説明。

  1. 中括弧で配列行の初期化の範囲を指定します。

  2. 中括弧で構造体の初期化の範囲を指定します。

  3. 中括弧で配列の初期化の範囲を指定します。

例 4-1では,次の出力を標準出力に書き込みます。

     row/col  ch      i            c
     -------------------------------------
     [0][0]:  a       1       3.000000e+10
     [0][1]:  b       2       4.000000e+10
     [0][2]:  c       3       5.000000e+10
     [1][0]:          0       0.000000e+00
     [1][1]:          0       0.000000e+00
     [1][2]:          0       0.000000e+00

注意
配列および構造体にディジグネーションを使用した初期化子の説明については, 第4.9節を参照してください。

4.8.5 共用体の初期化

共用体は,中括弧で囲んだ初期化子で初期化されます。この初期化子は, 共用体の最初のメンバのみを初期化します。次にその例を示します。

     static union
       {
          char ch;
          int i;
          float c;
       } letter = {'A'};

auto記憶域クラスを持つ共用体は,その共用体と同じ型の式でも初期化が可能です。 次にその例を示します。

     main ()
     {
     union1 {
         int i;
         char ch;
         float c;
       } number1 = { 2 };

     auto union2
       {
         int i;
         char ch;
         float c;
       } number2 = number1;
     }

4.9 ディジグネーションを使用した初期化子

C99規格に適合するように, Compaq Cは配列および構造体の初期化での ディジグネーションの使用をサポートしています。 (ディジグネーションは,コンパイラのコモンC,VAX C, Strict ANSI89モードではサポートされません。)

4.9.1 カレント・オブジェクト

C99初期化子では,カレント・オブジェクトおよびディジグネーション という概念を導入しました。

カレント・オブジェクトは,配列あるいは構造体の初期化時に,次に初期化されるものです。

ディジグネーションは,カレント・オブジェクトを設定する方法を提供します。 ディジグネーションが存在しない場合,カレント・オブジェクトのサブオブジェクトはオブジェクトの型に従って初期化されます。 配列要素は添字の昇順に, 構造体メンバは宣言順に初期化されます。

したがって,配列については,初期化が始まった際の最初のカレント・オブジェクトは a[0] です。各初期化子が使用されるに従い, カレント・オブジェクトは添字の昇順に次の初期化子に移行します。

同様に,構造体については,初期化が始まった際のカレント・オブジェクトは構造体内の最初の宣言です。 各初期化子が使用されるに従い,カレント・ オブジェクトは宣言順に次の初期化子に移行します。

4.9.2 ディジグネーション

C99規格では,ディジグネーションを含む中括弧で囲まれた初期化子並びを使用できます。 これは新しいカレント・オブジェクトを指定します。 ディジグネーションの構文は次のとおりです。

ディジグネーション:


      指名子並び=

指名子並び:


      指名子
      指名子並び  指名子

指名子:


      [定数式]
      .識別子

ディジグネーション内の指名子は,次の初期化子に指名子で記述されたオブジェクトの初期化を開始させます。 続いて,初期化は順次継続され,指名子で記述された次のオブジェクトから始まります。

配列については,指名子は次のとおりです。

   [ 凡整数定数式 ]

配列のサイズが不明である場合は,負数でない値が有効です。

構造体については,指名子は次のとおりです。

   .識別子

ここで,識別子は構造体のメンバです。

4.9.3

配列および構造体を初期化する従来の方法は,継続してサポートされています。 ただし,指名子の使用により,初期化子並びのコーディングを簡素化することができ, アプリケーション内の配列および構造体に対して行う将来的な変更に対応することができます。

  1. 指名子を使用することにより,配列要素はその順序に関係なく非ゼロ値に初期化することができます。
         int a[5] = { 0, 0, 0, 5 };  // Old way
    
         int a[5] = { [3]=5 };       // New way
    

    指名子[3]はa[3]を5に初期化します。

  2. 構造体のメンバは,その順序に関係なく非ゼロ値に初期化することができます。 次の例を参照してください。
          typedef struct {
               char flag1;
               char flag2;
               char flag3;
                int data1;
                int data2;
                int data3;
                } Sx;
    
         Sx = { 0, 0, 0, 0, 6 };   // Old way
    
         Sx = { .data2 = 6 };      // New way
    

    指名子.data2 は,構造体メンバの.data2を6に初期化します。

  3. 次は,配列に指名子を使用する別の例です。
         int a[10] = { 1, [5] = 20, 10 };
    

    この例では,配列要素は次のように初期化されます。

         a[0]=1
         a[1] through a[4] = 0
         a[5] = 20
         a[6] = 10
         a[7] through a[9] = 0
    

  4. 構造体に対する将来的な変更にも,その初期化子並びを変更することなく対応することができます。
          typedef struct {
               char flag1;
               char flag2;
               char flag3;
                int data1;
                int data2;
                int data3;
                } Sx;
    
         Sx = { 1, 0, 1, 65, 32, 18 };   // Old way
    
    
         Sx = { .flag1=1, 0, 1, .data1=65, 32, 18 }; // New way
    

    指名子.flag1 および.data1 を使用することにより,.flag1 の前あるいはflag3とdata1の間への将来的なフラグの追加を可能とします。

    指名子を順番に記述する必要はありません。たとえば,次の2つの初期化子並びは同等です。

         Sx = { .data1=65, 32, 18, .flag1=1, 0, 1 };
    
         Sx = { .flag1=1, 0, 1, .data1=65, 32, 18 };
    

  5. 単一指名子を使用することにより,配列の両端からスペースを割り当てることができます。
         int a[MAX] =
         {
             1, 3, 5, 7, 9, [MAX - 5] = 8, 6, 4, 2, 0
         };
    

    この例では,MAX が10よりも大きい場合,中間のいくつかの要素はゼロ値を持ちます。10 よりも小さい場合,最初の初期化子で指定されたいくつかの値は, 次に指定された5つの値によって上書きされます。

  6. 指名子はネストすることができます。
         struct { int a[3], b } w[] =
         { [0].a = {1}, [1].a[0] = 2 };
    

    この初期化は,次と同等です。

         w[0].a[0]=1;
         w[1].a[0]=2;
    

  7. ネストした指名子の別の例です。
         struct {
              int a;
              struct {
                   int b
                   int c[10]
              }x;
         }y = {.x = {1, .c = {[5] = 6, 7 }}}
    

    この初期化は,次と同等です。

         y.x.b = 1;
         y.x.c[5] = 6;
         y.x.c[6] = 7;
    

4.10 タグの宣言

次に示す構文は,構造体タグ,共用体タグ,または列挙型タグとして タグ識別子を宣言します。このタグ宣言が可視状態であれば, 次に続くタグ参照は宣言された構造体型,共用体型,または列挙型に置き換えることができます。 同じスコープ(可視状態の宣言)内のタグの次に続く参照は, 括弧で囲んだ並びを省略しなければなりません。タグの構文は次のとおりです。

struct タグ  { 宣言子並び }
union  タグ  { 宣言子並び }
enum   タグ  { 列挙子並び }

完全な構造体宣言または共用体宣言なしにタグが宣言されると,そのタグは不完全型を参照します。 不完全な列挙型は使用できません。不完全型は, 型が必要ではない場合にオブジェクトを指定するためにのみ有効です。 たとえば,型定義およびポインタ宣言を行うときなどです。型を完了するには,( 囲んだブロック内を除く)同じスコープ内のタグの別の宣言が内容を定義します。

次に示す構成では,testタグを使用して自己参照構造体を定義しています。

     struct test {
      float height;
      struct test *x, *y, *z;
     };

一度この宣言を行うと,次の宣言は struct test型のオブジェクトとして s を宣言し,struct test型のオブジェクトへのポインタとして sp を宣言します。

     struct test s, *sp;

注意
typedefキーワードを使用して同じことを行うこともできます。 構成は次のとおりです。
     typedef struct test tnode;
     struct test {
          float height;
          tnode *x, *y, *z;
     };
     tnode s, *sp;

4.11 型定義の宣言

記憶域クラス指定子が typedef である宣言では, 各宣言子は指定された型に別名を指定する typedef名を定義します。typedef宣言は新しい型を導入するのではなく, 指定した型の同義語を導入するだけです。次にその例を示します。

     typedef int integral_type;
     integral_type x;

この例では,integral_type int の同義語として定義され, 次の x の宣言は int型として x を宣言しています。 型定義は,長い型名(例:構造体や共用体の形式)を短縮形にする場合や, 型定義によってその型の解釈が簡単にできる場合に有効です。

typedef名は,通常の宣言の他の識別子と同じ名前空間を共有します。 オブジェクトを内側のスコープに再度宣言する場合,または同じスコープか内側のスコープ内の構造体メンバや共用体メンバとして宣言する場合には, その型指定子を内側の宣言から省略することはできません。 次にその例を示します。

     typedef signed int t;
     typedef int plain;
     struct tag {
        unsigned t:4;
        const t:5;
        plain r:5;
     };

この構成は不明瞭なものです。この例では,signed int型で t というtypedef名を宣言し, int型で plain というtypedef名を宣言し,3つのビット・フィールド・ メンバで構造体を宣言しています。これら3つとは,t という名前のメンバ,名前のないメンバ,および r という名前のメンバです。最初の2つのビット・ フィールド宣言は,1番目のビット・フィールドの型指定子が unsigned であるという点で異なっています。 前記の規則に従うと,t が構造体メンバの名前になります。2 番目のビット・フィールド宣言には const型修飾子が含まれ,これはまだ可視状態のままの typedef t を修飾するだけです。

次の例は,typedefキーワードの別の使用方法を示しています。

     typedef int miles, klicksp(void);
     typedef struct { double re, im; } complex;
        .
        .
        .
     miles distance;
     extern klicksp *metricp;
     complex x;
     complex z, *zp;

ここに示したコードはすべて有効です。distance の型は int であり,metricp の型は int を返す仮引数を持たない関数へのポインタです。 x z の型は,指定された構造体です。zp は構造体へのポインタです。

typedef名で使用される任意の型修飾子は,型定義の一部になるということに注意してください。 そのtypedef名を同じ型修飾子で後で修飾することはできません。 次にその例を示します。

     typedef const int x;
     const x y;            /*  Illegal -- duplicate qualifier used  */