3 データ型

Compaq Cではデータ・オブジェクトのにより,オブジェクトが表現できる値の範囲と種類, オブジェクトのために確保されるマシン記憶域のサイズ, およびオブジェクトで実行できる演算が決定されます。 また,関数にも型があり,関数の宣言に関数の返却値の型と仮引数型を指定することができます。

この章の各節では,次の内容について説明します。

どんな言語においても,指定したオブジェクトまたは関数のデータ型の選択は基本的なプログラミング・ ステップの1つです。プログラム中の各データ・ オブジェクトまたは関数にはデータ型を指定する必要があり,明示的に割り当てるかまたは省略時の設定により割り当てられます( オブジェクトへのデータ型の割当てについては, 第4 章を参照してください)。Compaq Cはさまざまなデータ型を提供しています。 この多様性がCompaq Cの強力な機能ですが,慣れないうちは混乱するかもしれません。

混乱を避けるために,Compaq Cの基本型は数種類と考えてください。 それ以外の型はこの基本型を組み合わせたものです。それらのいくつかは複数の方法で指定できます。 たとえば,short short int は同じ型です(本書では, 最も明瞭な最長の名前を使用します)。型は,宣言の一部として各オブジェクトまたは関数に割り当てます。 宣言についての詳細は, 第4章を参照してください。

表 3-1は,基本データ型の一覧です。 基本データ型には次の種類があります。

文字型は整数として格納されます。


注意
列挙型は通常,汎整数型として分類されますが, この一覧には基本データ型しか示していません。詳細については, 第3.6節を参照してください。

表 3-1 基本データ型

汎整数型 浮動小数点型
short int float
signed short int double
unsigned short int long double
int float _Complex (Alpha 専用)
signed int double _Complex (Alpha 専用)
unsigned int long double _Complex (Alpha 専用)
_Imaginary

Compaq Cでは,_Imaginaryキーワードを 使用すると警告が出力されます。 ただしこの警告は,このキーワードを通常の識別子として扱うようにすると解決します。

long int
signed long int
unsigned long int
long long int (Alpha 専用)
signed long long int (Alpha 専用)
unsigned long long int (Alpha 専用)
_Bool
汎整数文字型
char
signed char
unsigned char

汎整数型と浮動小数点型をまとめて,算術型と呼びます。整数値と浮動小数点値のサイズと範囲については, 第3.1 節を参照してください。

基本型からはさまざまな派生型を作成できます。派生型については, 第3.4節を参照してください。

基本型と派生型の他に,固有の型を指定するための voidenum,および typedef の3つのキーワードがあります。

さらに,型修飾子キーワードがあります。

オブジェクトの型宣言に修飾子キーワードを使用すると修飾型 になります。型修飾子の詳細については,第3.7節を参照してください。

このようにさまざまな型が存在するために,プログラムでは異なる型のオブジェクト上での演算が必要になる場合があります。 また,ある型の仮引数を別の仮引数型を返す関数に渡す必要が生じる場合もあります。 DEC Cでは異なる種類の変数を異なる方法で格納するため,最低1つのオペランドまたは実引数を変換して, そのオペランドまたは実引数の型をもう一方と一致させる必要があります。 変換は明示的にキャストを使用して行うか, またはコンパイラを介して暗黙に行うことができます。 データ型の変換については第6.11節を, 型の互換性については第2.7節を参照してください。

実行定義のデータ型については,プラットフォームに固有のDEC Cドキュメントを参照してください。

3.1 データ・サイズ

指定したデータ型のオブジェクトは,個別のサイズを持つメモリのセクションに格納されます。 異なるデータ型のオブジェクトには,それぞれ異なるメモリが必要です。 表 3-2は,基本データ型のサイズと範囲を示しています。

表 3-2 データ型のサイズと範囲

サイズ 範囲
汎整数型
short intまたはsigned short int 16 ビット -32768〜32767
unsigned short int 16ビット 0 〜65535
intまたはsigned int 32ビット -2147483648〜2147483647
unsigned int 32ビット 0〜4294967295
long intまたはsigned long int (OpenVMS) 32ビット -2147483648〜2147483647
long intまたはsignedlong int (Tru64 UNIX) 64ビット -9223372036854775808〜9223372036854775807
unsigned long int (OpenVMS) 32ビット 0 〜4294967295
unsigned long int (Tru64 UNIX) 64 ビット 0〜18446744073709551615
signed long long int (Alpha 専用)signed __int64 (Alpha 専用) 64ビット -9223372036854775808〜9223372036854775807
unsigned long long int (Alpha 専用)unsigned __int64 (Alpha 専用) 64ビット 0〜18446744073709551615
汎整数文字型
charおよびsigned char 8ビット - 128〜127
unsigned char 8 ビット 0〜255
wchar_ t 32ビット 0〜4294967295
浮動小数点型(範囲は絶対値用)
float 32ビット 1.1×10 -38 〜3.4×10 38
double 64ビット 2.2×10 -308 〜1.7×10 308
long double (OpenVMS, Alpha) 128ビット 3.4×10 -49321 〜1.2 ×10 -1049321
long double (OpenVMS VAX, Tru64 UNIX) doubleと同様 doubleと同様

派生型の場合は,さらにメモリ空間が必要になる可能性があります。

実行定義のデータ型のサイズについては,プラットフォームに固有の Compaq Cドキュメントを参照してください。

3.2 汎整数型

C言語では,次の汎整数型を宣言することができます。

汎整数型は次のとおりです。

3.2.1 非文字型

OpenVMSシステム上のCompaq Cでは,int long の記憶域は同一です。 さらに,signed int signed long の記憶域も同一であり, unsigned int unsigned long の記憶域も同一です。

Tru64 UNIXシステム上のCompaq Cでは,intデータ型の記憶域は32 ビットであり,long intデータ型の記憶域は64 ビットです。

64ビットの汎整数型signed long long intおよび unsigned long long intと,これらと同等の signed __int64およびunsigned __int64は, Alphaプロセッサ上でのみ提供されています。 注意: __int64データ型とlong long int データ型(符号付きおよび符号なし)は,ポインタ演算の場合を除き, 取り替えて使用することができます。 ポインタ演算の場合は,ポインタの型が同じでなければなりません。

__int64 *p1;
__int64 *p2;
long long int *p3;
   .
   .
   .
p1 = p2;        // valid
p1 = p3;        // invalid

符号付き汎整数型には,それぞれに対応する同じ記憶容量を使用する 符号なし汎整数型が存在します。汎整数型に unsignedキーワードを指定すると,記憶域の正の値の範囲を拡げることで整数値の解釈方法を変更できます。 unsignedキーワードを使用すると,ビットは異なる方法で解釈され, 符号なし型を持つ正の範囲を(負の範囲を狭めることによって) 拡げることができます。次にその例を示します。

     signed short int x = 45000;  /*  ERROR -- value too large for short int  */
     unsigned short int y = 45000;/*  This value is OK                        */

signed short int型の値の範囲は -32,768〜32,767 です。unsigned short int型の値の範囲は 0〜65,535です。

符号なしのオペランドを含む計算は,オーバフローすることはありません。 これは,unsigned型の範囲外の結果は剰余算術の規則によってその型に合うように調整されるからです。 結果を整数型で表せない場合には, その数が符号なし整数型で表すことができる最大値よりも大きいものとして結果を調整します。 つまり,下位ビットは保持され,結果の型に収まらない数値結果の上位ビットは破棄されます。 次にその例を示します。

     unsigned short int z = (99 * 99999); /*  Value of y after evaluation is 3965  */

Compaq Cでは,VAX Cおよび他のCコンパイラとの互換性のため,省略時の設定では単純なchar 型をsignedとして処理します。ただし,コマンド行オプションでこれを制御することが可能であり, 事前に定義されているマクロをテストして, 任意のコンパイルにおけるオプションの設定を判断することができます。Alpha システムでは,unsigned charを使用すると, 文字を多用する処理の効率が向上する場合があります。

nビットの符号なし整数は常に0〜2 n -1の範囲の変数で, 符号なしの2進表現に解釈されます。


注意
符号付き整数の解釈は,マシン表現のサイズとそのマシンで使用されるコード化の技法によって異なります。2 の補数表現の場合,nビットの符号付き整数の範囲は-2 n-1 〜2 n-1 -1 になります。

C99規定の_Boolデータ型は,コンパイラのVAX Cモード,コモン・モード, およびstrict ANSI89モード以外のすべてのモードで利用できます。 _Boolオブジェクトの記憶域は1バイトで, unsigned integer として扱われます。 ただし,その値は0か1だけです。


注意

3.2.2 文字型

文字型は,charキーワードで宣言される汎整数型です。 charオブジェクトは,非整数演算には使用しないようにしてください。 非整数演算に使用すると移植できなくなることがあります。 char型として宣言するオブジェクトは,常にソース文字集合の最大メンバを格納することができます。

次に有効な文字型を示します。

wchar_t ワイド文字型は,ASCII文字集合に含まれていない文字を表現するために提供されています。 wchar_ t型は,<stddef.h>ヘッダ・ファイルに typedefキーワードを使用して定義されています。定数または文字列に使用するワイド文字の直前には, L を付ける必要があります。次にその例を示します。

     #include <stddef.h>

     wchar_t a[6] = L"Hello";

charオブジェクトはすべて8ビットで格納されます。 wchar_tオブジェクトは unsigned intオブジェクトと同様に32ビットで格納されます。 指定した文字の値は使用している文字集合で決定されます。本書に記載しているすべての例ではASCII 文字集合を使用しています。10進基数,8 進基数,および16進基数に対応するASCII値の一覧については,付録 C を参照してください。

移植性を高めるために,算術に使用する charオブジェクトは signed char または unsigned char として宣言します。次にその例を示します。

     signed char letter;
     unsigned char symbol_1, symbol_2;
     signed char alpha = 'A';  /* alpha is declared and initialized as 'A' */

文字列はヌル文字 '\0'で終了する文字の配列です。 文字列使用の構文規則についての詳細は第1.9.3項を, 文字列リテラルの宣言については 第4章を参照してください。

3.3 浮動小数点型

浮動小数点型には,次の種類があります。

浮動小数点型は,小数部がある変数,定数,または関数の返却値に使用します。 また,値が汎整数型で格納可能な範囲を超える場合にも浮動小数点型を使用します。 次に,浮動小数点型宣言(および初期化)の例を示します。

     float x = 35.69;
     double y = .0001;
     double z = 77.0e+10;
     float Q = 99.9e+99;                 /*  Exceeds allowable range   */

3.3.1 複素数型 (Alpha 専用)

C99規格では,Fortranの型に似た組み込み複素数データ型が, 3つのすべての精度(float _Complexdouble _Complex,および long double _Complex)で導入されました。 C99規格には,これに関連するヘッダ・ファイル<complex.h> もあります。 <complex.h>ヘッダ・ファイルでは, "complex"というマクロを定義しています。 これらの型を参照するときには,このマクロを使用することをお勧め します(第9.2節を参照)。

複素数型の表現および境界合わせの要件は,対応する実数型の要素を 2つ含む配列型と同じです。 1番目の要素は複素数の実数部と同じで,2番目の要素は虚数部と同じです。

この型の使用方法は,Fortranの場合と似ています。 定数には,特別な構文はありませんが,実数部が0で虚数部が1.0の 複素数値を持つ新しいキーワード_Complex_Iがあります。 ヘッダ・ファイルには,_Complex_Iに展開されるマクロ I が定義されています。 このため,実数部と虚数部が同じ2.0の複素数定数は,2.0 + 2.0*Iと 記述できます。

Compaq Cの複素数型に関しては,次の注意事項があります。

3.3.2 虚数型 (Alpha 専用)

C99規格では,Annex G(参考)に規定されている「純虚数」型という 実験的なオプションの機能とともに使用する型指定子として, _Imaginaryキーワードが予約されています。 Compaq Cでは,_Imaginaryキーワードを使用すると 警告が出力されます。 この警告は,このキーワードを通常の識別子として扱うようにすると 解決します。

3.4 派生型

C言語の派生型には,次の5種類があります。

これ以降の各項では,この派生型の5種類について説明します。

派生型は,1つ以上の基本型の組み合わせから形成されています。 派生型を使用すれば,多くの新しい型を形成することができます。 配列型と構造体型をまとめて, 集成型と呼びます。集成型には共用体型は含まれませんが,共用体には集成体メンバを含めることが可能です。

3.4.1 関数型

関数型は指定された型の値を返す関数について記述します。関数が値を返さない場合には, 次のように「void を返す関数」として宣言しなければなりません。

     void function1 ();

次の例では,関数のデータ型は「int を返す関数」です。

     int uppercase(int lc)
     {
       int uc = lc + 0X20;
       return uc;
     }

一般的な宣言については,第4章で説明します。 関数については第5章で,その宣言, 仮引数,および実引数の引渡しについて説明します。

3.4.2 ポインタ型

ポインタ型は,指定された型のオブジェクトのアドレスを表す値を記述します。 ポインタは,それが示すオブジェクトのアドレスを参照する整数値として格納されます。 ポインタ型は他の型から派生する型であり,ポインタの 被参照型と呼ばれます。次にその例を示します。

     int *p;          /*  p is a pointer to an int type                 */
     double *q();     /*  q is a function returning a pointer to an
                          object of type double                         */
     int (*r)[5];     /*  r is a pointer to an array of five elements   */
                      /*  (r holds the address to the first element of
                          the array)                                    */
     const char s[6]; /*  s is a const-qualified array of 6 elements    */

ポインタ自体はどんな記憶域クラスでも持つことができますが,ポインタに示されるオブジェクトは register記憶域クラスを持つことはできません。 また,ビット・フィールドにすることもできません。 修飾済み互換型または未修飾の互換型へのポインタは,そのポインタが示す型と同じ表現および境界調整条件を持ちます。 これは互換型の場合のみであり, 他の型の場合にはあてはまりません。

void * は汎用的な「void へのポインタ型」を指定します。void * を使用すれば,任意の型のオブジェクトを指定することができます。これは主に, 異なる型または不明の型(関数プロトタイプなど)を持つオブジェクトのアドレスを示すためにポインタが必要な場合に役に立ちます。 さらに, void へのポインタは別の型のポインタと変換することもでき, 文字型のポインタと同じ表現と境界調整条件を持ちます。

アドレス0へのポインタを空ポインタと呼びます。並びの次のメンバを示す際にポインタを使用しますが, 並びのメンバがそれ以上存在しないことを示す場合は空ポインタを使用します。 ただし,空ポインタを * または添字指定演算子で逆参照した場合の結果は保証できません。

ポインタ宣言の構文については,第4 章を参照してください。

3.4.3 配列型

配列型は,有効な完了型から形成することができます。配列型を完了するには, 配列メンバの数と型が明示的または暗黙に指定されていなくてはなりません。 メンバ型は,同じコンパイル単位または異なるコンパイル単位で完了できます。 配列は, void型または関数型にすることはできません。これは, void型は完了することができず,関数型は記憶域を必要とするオブジェクト型ではないからです。

一般的に,配列はいくつかの同種類の値で演算を実行するために使用します。 配列型のサイズは,配列のデータ型と要素数によって決定されます。 配列の各要素は同じ型を持っています。たとえば,次の定義では4文字の配列を作成します。

     char x[] = "Hi!"   /*  Declaring an array x   */;

要素のそれぞれが,charオブジェクトのサイズ(8ビット)を持っています。 配列のサイズは初期化の時点で決定されます。この例では, 配列は3つの明示的な要素と1つのヌル文字を持っています。4つの8ビットの要素は,32 ビットのサイズを持つ配列になります。

配列は連続してメモリに割り当てられ,空(つまりメンバを持たない状態) にすることはできません。1つの配列は1次元でしか作成できません。「2 次元」の配列を作成するには,配列の配列を宣言してください。

サイズの不明な配列を宣言することができます。この宣言はサイズが指定されないため, 不完全配列宣言と呼びます。次の例は不完全な宣言を示しています。

     int x[];

この方法で宣言する配列サイズは,プログラムのどこかで指定しなければなりません( 不完全配列と配列の初期化については,第4.7節を参照してください)。

文字列(文字列リテラル)は char または wchar_t型の形式で格納され,ヌル文字'\0' で終了します。

Compaq Cでは,1つの配列は1次元でしか作成できません。ただし, 配列の配列を宣言することにより,多次元配列を作成することができます。 これらの配列の要素は,右端の添字が最初に変更するように, 増加方向のアドレスに格納されます。これは行優先順序と呼ばれるもので, 車の走行距離計に似ています。たとえば,int a[2][3]; として宣言した2つの配列では,次の順序で要素が格納されます。

     a[0][0], a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]

3.4.4 構造体型

構造体型は,メンバと呼ばれる空以外のオブジェクトが連続して割り当てられている集合です。 構造体を使用すれば,異質のデータをグループ化することができます。 これはPascal言語のレコードと類似しています。 配列とは異なり,構造体の要素は同じデータ型の要素である必要はありません。 また,構造体の要素は名前でアクセスされ, 添字ではアクセスされません。次の例では,employee 構造体を宣言しています。 また,employee という構造体型の2 つの構造体変数(ed および mary )を宣言しています。

     struct employee { char name[30]; int age; int empnumber; };
     struct employee ed, mary;

構造体メンバには,void型や関数型のような不完全型でなければ, どんな型でも指定することができます。構造体にはそれ自体の型のオブジェクトへのポインタを含めることはできますが, それ自体の型のオブジェクトをメンバとして含めることはできません。 このようなオブジェクトは, 不完全型を持つことになります。次に無効な例を示します。

     /*  This is invalid. */
     struct employee {
       char name[30];
       struct employee div1;       /*  This is invalid. */

       int *f();                   /*  This is also invalid. */
     };

次に有効な例を示します。

     struct employee {
       char name[30];
       struct employee *div1;/*  Member can contain pointer to employee
                                 structure.                             */
       int (*f)();           /*  Pointer to a function returning int    */
     };

宣言された構造体メンバ名は,その構造体内では固有でなければなりません。 ただし,ネストされた構造体またはネストされていない別の構造体, あるいは異なるオブジェクトを参照している名前空間ではそのメンバ名を使用することができます。 次にその例を示します。

     struct {
       int a;
       struct {
         int a;  /* This 'a' refers to a different object
                    than the previous 'a'               */
       } nested;
     };

拡張機能として,Compaq Cの緩和モードでは, 構造体または共用体でメンバの名前を指定せずに (無名メンバと呼ぶ), ネストされた構造体または共用体を宣言することができます。 この効果は,ネストされた無名の構造体または共用体のメンバを, 構造体または共用体にネストすることなく, 直接宣言したのと同じです。 そのため,前の例で, 内部のstruct宣言から識別子 nestedを省略した場合, 内部の構造体のメンバaと, それが属している構造体のメンバaとの間で矛盾が発生するため, エラーになります。 これは,(構造体と共用体の両方に許可されていることを除き) C++言語の無名共用体機能と類似しており, またVAX C固有のvariant_structおよび variant_union機能と類似しています。

構造体とその宣言については,第4 章を参照してください。

コンパイラは,メンバ宣言の順に構造体メンバの記憶域を割り当てます。 この順序では,後続のメンバが増加するメモリ・アドレスに割り当てられます。 最初のメンバは,必ずその構造体自体の開始アドレスから始まります。 後続のメンバは境界調整単位ごとに境界調整されますが, これは構造体のメンバ・サイズによって異なることがあります。 構造体は,そのメンバが正しく境界調整されるようにパディング( 未使用のビット)を含んでいることがあります。この場合,その構造体のサイズは, すべてのメンバに必要な記憶容量に境界調整のために必要なパディング領域用の記憶容量を追加したものになります。 構造体の境界調整および表現に関するプラットフォーム固有の情報については, ご使用のシステムの Compaq Cのマニュアルを参照してください。

あるプラットフォーム上の構造体の境界調整と,他のプラットフォーム上の境界調整とを一致させるためには, プラグマを使用します。プラグマについての詳細は, 第B.29 節を参照してください。

3.4.5 共用体型

共用体型には,メモリの同じ位置に異なる型のオブジェクトを格納できます。 それぞれ異なる共用体メンバは,プログラム中の同じ位置を異なる時期に占有できます。 共用体の宣言には共用体のすべてのメンバを含めることができ, 共用体が保持できるすべてのオブジェクト型も入れることができます。 共用体には一度に1つのメンバしか入れることができません。共用体に他のメンバを続けて代入すると, その同じ記憶域にある既存のオブジェクトは上書きされます。

共用体には有効な識別子の名前を付けることができます。空の共用体を宣言することはできず, 共用体にそれ自体のインスタンスを含めることもできません。 共用体のメンバとして,void型,関数型,または不完全型は指定できません。 共用体には,これらの型の共用体へのポインタを含めることができます。

見方を変えると,共用体とは異なる時期に異なる型のオブジェクトを表すことのできる単一オブジェクトであるといえます。 共用体によって,マシン依存の構成を使用せずに, プログラムの処理状況に応じて型とサイズを変更できるオブジェクトを使用することができます。 別の言語では,この概念を 可変レコードと呼んでいます。

共用体を定義するための構文は,構造体の構文と非常に類似しています。 各共用体型の定義は,それぞれ固有の型を作成します。共用体内では,各共用体メンバ名は固有でなければなりません。 しかし,ネストされた共用体やネストされていない他の共用体または名前空間では, 同じメンバ名を使用できます。 次にその例を示します。

     union {
       int a;
       union foo {
         int a;  /* This 'a' refers to a different object
                    than the previous 'a'                */
       } nested;
     };

Compaq Cの拡張機能である緩和モードでは, C++言語と同じように無名の共用体メンバが許されることに注意してください。

共用体のサイズは,その最大のメンバに境界調整条件に必要なパディングを加えた記憶容量になります。

一度共用体を定義すると,その共用体に宣言されているオブジェクトに値を代入することができます。 次にその例を示します。

     union name {
       dvalue;
       struct x { int value1; int value2; };
       float fvalue;
     } alberta;
     alberta.dvalue = 3.141596; /* Assigns the value of pi to the union object */

この場合,alberta double値,struct値,または float値を保持できます。プログラマは,共用体に含まれているオブジェクトの現在の型を追跡する必要があります。 代入式を使用して, 共用体に保持される値の型を変更できます。

ある型の値を格納するために共用体を使用して,その値を別の型でアクセスした場合の処理結果は定義されていません。 次にその例を示します。

     /*
         Assume that `node' is a typedef_name for objects for which
         information has been entered into a hash table;

         `hash_entry' is a structure describing an entry in the hash table.
         The member `hash_value' is a pointer to the relevant `node'.
      */
     typedef struct hash_entry
     {
        struct hash_entry *next_hash_entry;
        node   *hash_value;
        /* ... other information may be present ... */
     } hash_entry;

     extern hash_entry *hash_table [512];


     /*
         `hash_pointer' is a union whose members are a pointer to a
         `node' and a structure containing three bit fields that
         overlay the pointer value.  Only the second bit field is
         being used, to extract a value from the middle
         of the pointer to be used as an index into the hash table.
         Note that nine bits gives a range of values from 0 to 511;
         hence, the size of `hash_table' above.
      */
     typedef union
     {
        node *node_pointer;
        struct
        {
         unsigned : 4;
         unsigned  index : 9;
         unsigned :19;
        } bits;
     } hash_pointer;

3.5 void型

void型は,完了することができない不完全型です。

void型には,次に示す重要な3つの使用方法があります。

次の例では,仮引数を持たず,値を返さない関数を定義するために void を使用する方法を示します。

     void message(void)
     {
       printf ("Stop making sense!");
     }

次の例では,1番目と2番目の実引数として,任意のオブジェクトへのポインタを受け付ける関数の関数プロトタイプを示します。

     void memcopy (void *dest, void *source, int length);

void型へのポインタは,文字型へのポインタと同じ表現および境界調整条件を持ちます。 void *型は voidを基本にした派生型です。

また,void型をキャスト式に使用して明示的に値を破棄したり, または値を無視することもできます。

     int tree(void);

     void main()
     {
       int i;

       for (; ; (void)tree()){...}  /* void cast is valid                  */

       for (; (void)tree(); ;){...} /* void cast is NOT valid, because the */
                                    /* value of the second expression in a */
                                    /* for statement is used               */

       for ((void)tree(); ;) {...}  /* void cast is valid                  */

     }

void式は値を持たないため,値が必要な場合はいかなる状況であっても使用できません。

3.6 列挙型

列挙型は,定義済み並びからオブジェクトの許容値を指定するために使用します。 並びの要素は列挙定数と呼ばれます。列挙型の主な使用方法は, シンボル名を明示的に表すことです。したがって,値を整数値で表すことができるオブジェクトを意図しています。

列挙型のオブジェクトは signed int型のオブジェクトとして解釈され, 他の汎整数型のオブジェクトと互換性があります。

コンパイラは,各列挙定数に整数値を(0から開始して)自動的に代入します。 次の例では,列挙定数の並びを持つ background_ color列挙型オブジェクトを宣言しています。

     enum colors { black, red, blue, green, white } background_color;

このプログラムでは,後で background_colorオブジェクトに値を代入できます。

     background_color = white;

この例では,コンパイラが整数を自動的に代入します( black = 0,red = 1,blue = 2,green = 3,white = 4)。この代わりに, 列挙型の定義中に明示的に値を代入することもできます。

     enum colors { black = 5, red = 10, blue, green = 7, white = green+2 };

この場合,black は整数値5 ,red は10,blue は11,green は7,white は9に等しくなります。blue は直前の定数値( red )に1を加えた値に等しく,次の green はこの番号の連続からは外れます。

ANSI C規格では列挙型への代入については厳密ではないため,定義済み並びにない代入値でも何の警告もなしに受け付けられます。

3.7 型修飾子

型修飾子には,次の4つがあります。

型修飾子はANSI C規格によって導入されているものであり,コンパイラの最適化をある程度制御できるようにするものです。 const およびvolatile 型修飾子は,任意の型に適用することができます。 __restrict 型修飾子はポインタ型にのみ適用できます。

__restrict 型修飾子は,1989 ANSI C規格に含まれていないため, このキーワードには先頭に2つのアンダスコア(_)が付けられています。C 規格の次のバージョン(9X)では,この節の記述と同じ意味で,キーワード restrict が採用される予定です。

const を使用するとオブジェクトへの書込みアクセスを制御でき, そのオブジェクトを含む関数呼出しの間で起こり得る副作用を排除できます。 これは,通常は副作用によりオブジェクトの記憶域が変更されるのに対して const がこの変更を禁止するからです。

他のプロセスまたはハードウェアで変更される可能性のあるオブジェクトには, volatile を使用してください。 volatile を使用すると,オブジェクトの参照に関する最適化が行われなくなります。 オブジェクトが volatile で修飾されている場合には,初期化されてから代入されるまでの間にそれが変更される可能性があるため最適化することはできません。

すべての関数の仮引数が1つの仮引数の型修飾を共有することはありません。 次にその例を示します。

     int f( const int a, int b)   /*  a is const qualified; b is not  */

配列識別子とともに型修飾子を使用すると配列の要素は修飾されますが, 配列型自体は修飾されません。

次の宣言と式は,型修飾子が配列型または構造体型を変更する場合を示しています。

     const struct s { int mem; } cs = { 1 };
     struct s ncs;                        /* ncs is modifiable         */
     typedef int A[2][3];
     const A a = {{4, 5, 6}, {7, 8, 9}};  /*  array of array of const  */
                                          /*  int's                    */
     int *pi;
     const int *pci;

     ncs = cs;            /*  Valid                                    */
     cs = ncs;            /*  Invalid, cs is const-qualified           */
     pi = &ncs.mem;       /*  Valid                                    */
     pi = &cs.mem;        /*  Violates type constraints for = operator */
     pci = &cs.mem;       /*  Valid                                    */
     pi = a[0];           /*  Invalid; a[0] has type "const int *"     */

3.7.1 const型修飾子

値を変更できないオブジェクトを修飾するには, const型修飾子を使用します。 constキーワードで修飾したオブジェクトは,変更することはできません。 つまり,const として宣言されているオブジェクトは, その値を変更する演算時にオペランドとしての役割を果たすことはできません。 たとえば, ++ および -- 演算子は const で修飾されるオブジェクトでは使用できません。オブジェクトに const修飾子を使用すると,記憶域を変更する操作によって引き起こされる副作用を防ぐことができます。

const を修飾したオブジェクトの宣言は,非修飾型のものよりも多少複雑になる可能性があります。 次に,いくつかの例とその説明を示します。

     const int x = 44;   /*  const qualification of int type.
                             The value of x cannot be modified. */  
     const int *z;       /*  Pointer to a constant integer.
                             The value in the location pointed 
                             to by z cannot be modified.        */
     int * const ptr;    /*  A constant pointer, a pointer  
                             that will always point to the 
                             same location                      */
     const int *const p; /*  A constant pointer to a constant
                             integer: neither the pointer or 
                             the integer can be modified.       */
     const const int y;  /*  Illegal - redundant use of const   */

次は,const型修飾子に適用される規則です。

3.7.2 volatile型修飾子

volatile型修飾子を含むオブジェクトは,そのオブジェクトの参照または修正を変更するコンパイラの最適化の対象としてはならないことを示しています。


注意
volatileオブジェクトは, 特に副作用を引き起こす傾向があります(第2.5節を参照してください)。

volatile を使用することによって無効になる最適化は, 次のように分類できます。

volatile指定子を持たないオブジェクトがコンパイラに対してこのような最適化を強制することはありません。 コンパイラは,プログラムの状況とコンパイラ最適化レベルによって, 最適化を自由に適用します。

volatile修飾子は,コンパイラに volatileオブジェクトのメモリを割り当てさせて,そのオブジェクトを常にメモリからアクセスさせます。 この修飾子は,コンパイラを制御する方法以外の方法でオブジェクトをアクセスできるように宣言する際によく使用されます。 したがって,volatileキーワードで修飾したオブジェクトは, 他のプロセッサまたはハードウェアによってそれぞれの方法で変更またはアクセスされる可能性があります。 さらに,副作用の影響を特に受けやすくなります。

次の規則は,volatile修飾子を使用する場合に適用されます。

3.7.3 __unaligned型修飾子

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

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

次は,__unaligned の一般的な使用例です。

     typedef enum {int_kind, float_kind, double_kind} kind;
     void foo(void *ptr, kind k) {
         switch (k) {
         case int_kind:
             printf("%d", *(__unaligned int *)ptr);
             break;
         case float_kind:
             printf("%f", *(__unaligned float *)ptr);
             break;
         case double_kind:
             printf("%f", *(__unaligned double *)ptr);
             break;
         }
     }

3.7.4 __restrict型修飾子

ポインタについてコンパイラによる最適化を行うことを示すには,ポインタ型の宣言で __restrict 型修飾子を使用します。限定ポインタは,ISO C 規格の9Xリビジョンに追加される予定です。限定ポインタを適切に使用すると, 多くの場合,コンパイラによるコード出力の質を改善することができます。

3.7.4.1 理由

以降の各項では,限定ポインタをサポートする理由について説明します。

3.7.4.1.1 エイリアシング

単に値をレジスタに保持しておくことからループの並列実行に至るまで, 多くのコンパイラの最適化を行うためには,2つの異なる左辺値が異なるオブジェクトを示しているかどうかを判断する必要があります。 オブジェクトが異なっていない場合, それらの左辺値はエイリアスであると言われます。2 つの左辺値がエイリアスであるかどうかをコンパイラが判断できない場合は, それらがエイリアスであると仮定して,さまざまな最適化をやめなければなりません。

単一の関数内,あるいは単一のコンパイル単位内でさえ,2つのポインタが同じオブジェクトを指しているかどうかを判断するために利用できる情報は十分でないため, ポインタを介したエイリアシングは大きな困難を伴います。 また,情報が十分に利用できたとしても,この分析にはかなりの時間とスペースを必要とします。 たとえば,関数の仮引数であるポインタが取り得る値を判断するには, プログラム全体を分析する必要があります。

3.7.4.1.2 ライブラリの例

2つの標準Cライブラリ関数memmovememcpyのCの実現において,どのように潜在的なエイリアシングが入るかについて考えてみます。

次の例は,memcpy 関数とmemmove 関数の実現のサンプルを対比させています。

     /* Sample implementation of memmove */

        void *memmove(void *s1, const void *s2, size_t n) {
                char * t1 = s1;
                const char * t2 = s2;
                char * t3 = malloc(n);
                size_t i;
                for(i=0; i<n; i++) t3[i] = t2[i];
                for(i=0; i<n; i++) t1[i] = t3[i];
                free(t3);
                return s1;
        }

     /* Sample implementation of memcpy */

        void *memcpy(void *s1, const void *s2, size_t n);
                char * t1 = s1;
                const char * t2 = s2;
                while(n-- > 0) *t1++ = *t2++;
                return s1;
        }

memcpy の制限は,規格の説明には示されていますが,C の実現において直接表すことはできません。このため, memmove での一時配列の使用を削除するというソース・ レベルの最適化はできますが,結果として得られる単一のループに対してのコンパイラの最適化は提供されません。

多くのアーキテクチャでは,バイトをブロック単位でコピーする方が,一 度に1バイトづつコピーするよりも速く処理することができます。

3.7.4.1.3 相互に重なり合うオブジェクト

memcpy の規格での説明にある制限では,相互に重なり合うオブジェクト 間でのコピーを禁止しています。オブジェクト とはデータ記憶域の領域のことであり,ビット・フィールドを除いて,1 あるいはそれ以上のバイトの連続した並び(数,順番,およびエンコーディングは, 明示的に指定されているか,または実装者が定義する。) で構成されます。

次の例について考えてみてください。

     /* memcpy between rows of a matrix */

     void f1(void) {
             extern char a[2][N];
             memcpy(a[1], a[0], N);
     }

この例から,次のことがわかります。

では,次の例について考えてみましょう。

     /* memcpy between halves of an array */

     void f2(void) {
             extern char b[2*N];
             memcpy(b+N, b, N);
     }

この例からは,次のことがわかります。

オブジェクトの長さは,さまざまな方法で決定されます。

3.7.4.1.4 memcpyのための限定ポインタのプロトタイプ

memcpy に関する制限と同様,エイリアシング制限も関数定義として表すことができるのであれば, コンパイラは効果的にポインタのエイリアスの分析を行うために利用することができるでしょう。 __restrict 型修飾子は,ポインタの宣言で,ポインタが malloc への呼出しで初期化されたかのように, ポインタが指し示すオブジェクトに対して,排他的な初期のアクセス を提供することを指定することによって,これを実現します。

次に示すmemcpy のためのプロトタイプは,必要な制限を表すとともに, 現在のプロトタイプと互換性があります。

     void *memcpy(void * __restrict s1, const void * __restrict s2, size_t n);

3.7.4.2 __restrict型修飾子の正式な定義

次に示す限定ポインタの定義では,できるだけ多数の典型的な例におけるエイリアシング制限の式をサポートしています。 これは,既存のプログラムを変換して, 限定ポインタを使用する際に有用であり,また,新規のプログラムでは, より自由にスタイルを選択できるようになります。

このため,この定義では,限定ポインタについて次のことが可能です。

定義

ポインタは,宣言で__restrict 型修飾子を指定することにより, 限定ポインタとして指定されます。

改訂ISO C規格に含まれる予定の限定ポインタの正式な定義は,次のとおりです。<blockquote>

Dは,オブジェクトPを限定修飾ポインタとして指定する方法を提供する通常の識別子の宣言とする。

Dがブロックの内部にあり,記憶域クラスexternを持たないとき,B はそのブロックを示すものとする。Dが関数定義の仮引数宣言の並びにあるとき,B はそれに関連するブロックを示すものとする。それ以外の場合には,B はmainのブロック(または,フリースタンディング環境において, プログラムの開始時に呼ばれる関数のブロック) を示す。

以降で,(ポインタ式Eを評価する前であってBを実行しているある評価順序点において) P が以前指していた配列オブジェクトのコピーを指すようにP を修正するとEの値が変わるとき,EはオブジェクトP に基づいているという。つまり,Eは,Pを介して間接的に参照されるオブジェクトの値ではなく,P の値自体に依存している。 たとえば,識別子p(int ** restrict) という型を持つとすると,ポインタ式 p およびp+1 は,p で指定された限定ポインタ・オブジェクトに基づいているが,ポインタ式 *p およびp[1] は基づいていない。

Bの各実行中には,Oは,Pに基づくポインタ式を介するすべての参照によって動的に決定される配列オブジェクトとする。O の値へのすべての参照は,P に基づくポインタ式を介して行われなければならない。 さらに,Pが,別の限定ポインタ・オブジェクトP2に基づいているポインタ式E の値を割り当てられ,ブロックB2に関連付けられていれば,B2 のいずれかの実行は,Bの実行の前に開始されるか, あるいは,B2の実行はその割り当ての前に終了しなければならない。 この条件を満足しない場合,動作は未定義となる。

ここで,Bの実行とは,Bに関連付けられていて自動記憶域存続期間を持つオブジェクトのインスタンスのために記憶域の予約が保証されている間の, プログラムの実行の一部という意味である。値の参照とは, 値へのアクセスまたは値の変更のことである。Bの実行中, 実際に評価される参照だけが調べられる。(評価されない式での参照や, 可視識別子を使用するという意味において「利用可能」であるが, 実際にはBのテキストに現れない参照は除外される。)

翻訳プログラムは,限定ポインタの使用によるすべてまたは任意のエイリアシングを自由に無視することができる。</blockquote>

3.7.4.3

__restrict 型修飾子の正式な定義は分かりにくいものですが, 説明を簡略にすると不正確で不完全になってしまいます。この定義の本質は, __restrict 型修飾子は,限定ポインタを介してメモリ・ アクセスが行われるときにはいつでもプログラマによってなされる表明であり, コンパイラが考慮する必要のある唯一のエイリアスは, 同じポインタを介して行われる別のアクセスである,ということです。

複雑に感じられる原因の多くは,ポインタを介して行われるアクセスが何を意味するのかを厳密に定義したり( 基づくの規則),ブロック境界においてのみ起こりうるエイリアシングを制限しながら, 限定ポインタに別の限定ポインタの値を代入する方法を指定したりする点にあります。 限定ポインタについて理解する最善の方法は,例を参照することです。

次の例は,さまざまな文脈における限定ポインタの使用方法を示しています。

3.7.4.3.1 ファイル・スコープ限定ポインタ

ファイル・スコープ限定ポインタは,非常に強い制限を受けます。このポインタは, プログラムが存続している間,単一の配列オブジェクトを指していなければなりません。 その配列オブジェクトは,限定ポインタを介して参照されてもならず, その宣言された名前(ある場合)または別の限定ポインタを介して参照されてもなりません。

これらの制限のため,ポインタを介した参照は,その宣言された名前で静的配列を参照するのと同様に効率的に最適化することができます。 したがって, ファイル・スコープ限定ポインタは,動的に割り当てられるグローバル配列へのアクセスを提供する際に有用です。

次の例では,コンパイラは__restrict 型修飾子から,名前 ab,およびc の間で潜在的なエイリアシングが存在しないことを推測することができます。

     /* File Scope Restricted Pointer */

     float * __restrict a, * __restrict b;
     float c[100];

     int init(int n) {
         float * t = malloc(2*n*sizeof(float));
         a = t;       /* a refers to 1st half. */
         b = t + n;   /* b refers to 2nd half. */
        }

関数init では,割り当てられた記憶域の単一のブロックが2 つの別個の配列に分けられています。

3.7.4.3.2 関数の仮引数

限定ポインタは関数のポインタ仮引数としても非常に有用です。次の例について考慮してみてください。

     /* Restricted pointer function parameters */

     float x[100];
     float *c;

     void f3(int n, float * __restrict a, float * const b) {
         int i;
         for ( i=0; i<n; i++ )
             a[i] = b[i] + c[i];
     }
     void g3(void) {
         float d[100], e[100];
         c = x; f3(100,   d,    e); /* Behavior defined.   */
                f3( 50,   d, d+50); /* Behavior defined.   */
                f3( 99, d+1,    d); /* Behavior undefined. */
         c = d; f3( 99, d+1,    e); /* Behavior undefined. */
                f3( 99,   e,  d+1); /* Behavior defined.   */
     }

関数f3 では,コンパイラは,変更されたオブジェクトのエイリアシングはないと推断して, 積極的にループの最適化を行う可能性があります。 f3 に入ると,限定ポインタa はそれに関連する配列への排他的なアクセスを提供する必要があります。 特に,f3 の内部では,bca に基づくポインタ値を代入されていないため,どちらも a に関連する配列内を指していない可能性があります。 b については,宣言のconst修飾子から明らかですが, c については,f3の本体の検査が必要になります。

g3 に示す2つの呼出しは,結果として, __restrict 修飾子と矛盾するエイリアシングとなるため, それらの動作は未定義となります。c が,b に関連している配列内を指すことは許されています。 また,この目的のため,特定のポインタに関連する「配列」は,そのポインタを介して実際に参照される配列オブジェクトの一部分のみを意味します。

3.7.4.3.3 ブロック・スコープ

ブロック・スコープ限定ポインタは,そのブロックに制限されたエイリアシング表明を行います。 これは,表明に関数スコープを持たせるよりも自然です。 たとえば,主要なループにのみ適用されるローカル表明ができるようにします。 また,関数をマクロに変換することにより関数をインライン化する際に同様の表明を行うことができるようにします。

次の例では,元の限定ポインタ仮引数は,ブロック・スコープ限定ポインタで表されています。

     /*  Macro version of f3 */

     float x[100];
     float *c;

     #define f3(N, A, B)                                    \
     {   int n = (N);                                       \
         float * __restrict a = (A);                          \
         float * const    b = (B);                          \
         int i;                                             \
         for ( i=0; i<n; i++ )                              \
             a[i] = b[i] + c[i];                            \
     }

3.7.4.3.4 構造体のメンバ

構造体の限定ポインタ・メンバはエイリアシング表明を行います。この表明のスコープは, この構造体にアクセスするために使用される通常の識別子のスコープです。

したがって,次の例では,構造体の型はファイル・スコープで宣言されていますが, f4 の仮引数の宣言によって行われている表明は,( 関数の)ブロック・スコープを持ちます。

     /* Restricted pointers as members of a structure */

     struct t {     /* Restricted pointers assert that    */
         int n;     /* members point to disjoint storage. */
         float * __restrict p;
         float * __restrict q;
     };

     void f4(struct t r, struct t s) {
         /* r.p, r.q, s.p, s.q should all point to    */
         /* disjoint storage during each execution of f4. */
         /* ... */
     }

3.7.4.3.5 型定義

typedef 内の__restrict 修飾子は, オブジェクトへのアクセスを提供する通常の識別子の宣言で typedef 名を使用する際にエイリアシング表明を行います。 構造体のメンバに関しては,typedef 名のスコープではなく, 通常の識別子のスコープが,エイリアシング表明のスコープを決定します。

3.7.4.3.6 限定ポインタに基づく式

次の例について考慮してみてください。

     /* Pointer expressions based on p */

     #include <stdlib.h>
     #include <string.h>

     struct t { int * q; int i; } a[2] = { /* ... */ };

     void f5(struct t * __restrict p, int c)
     {
          struct t * q;
          int n;
          if(c) {
              struct t * r;
              r = malloc(2*sizeof(*p));
              memcpy(r, p, 2*sizeof(*p));
              p = r;
          }
          q = p;
          n = (int)p;

          /* - - - - - - - - - - - - - - - - - - - - - - -

            Pointer expressions     Pointer expressions
            based on p:             not based on p:
            -------------------     -------------------
            p                       p->q
            p+1                     p[1].q
            &p[1]                   &p
            &p[1].i
            q                       q->p
            ++q
            (char *)p               (char *)(p->i)
            (struct t *)n           ((struct t *)n)->q
          - - - - - - - - - - - - - - - - - - - - - - - - */
     }

     main() {
         f5(a, 0);
         f5(a, 1);
     }

この例では,限定ポインタ仮引数p は,2つの構造体の元の配列のコピーを指すように潜在的に調整されます。 定義により, その後のポインタ式は,その値がこの調整によって変更される場合に限り, p に基づくと言われます。

コメント部分の説明は次のとおりです。

この式に関する適切なprint文を追加し,main 内での f5 の2つの呼出しによって生成された値を比較することにより, このことを確認することができます。

「基づく」の定義は,処理系定義の動作に依存する式に適用されます。 これについては例を参照してください。例では,後ろに(struct t *)が続く(int) のキャストは,元の値を提供すると想定しています。

3.7.4.3.7 限定ポインタ間での代入

最初の限定ポインタが関連付けられているブロックの実行を,2番目のポインタに関連付けられているブロックの後に開始した場合, 一方の限定ポインタがもう一方の限定ポインタより「新しい」と想定します。 このとき, 正式な定義では,新しい限定ポインタには,古い限定ポインタに基づく値を代入することができます。 これにより,たとえば,限定ポインタ仮引数を持つ関数を, 限定ポインタである実引数を指定して呼び出すことができます。

逆に,古い限定ポインタには,新しい限定ポインタに関連付けられているブロックの実行が終了した後にのみ, 新しい限定ポインタに基づく値を代入することができます。 これにより,たとえば,関数にローカルな限定ポインタの値を関数が返した後, その返却値を別の限定ポインタに代入することができるようになります。

プログラムに,これら2つのカテゴリのいずれにも当てはまらない2つの限定ポインタ間での代入が含まれている場合, このプログラムの動作は未定義となります。 次の例を参照してください。

     /* Assignments between restricted pointers */

     int * __restrict p1, * __restrict p2;

     void f6(int * __restrict q1, * __restrict q2)
     {
         q1 = p1;     /* Valid behavior     */
         p1 = p2;     /* Behavior undefined */
         p1 = q1;     /* Behavior undefined */
         q1 = q2;     /* Behavior undefined */
         {
             int * __restrict r1, * __restrict r2;
             ...
             r1 = p1; /* Valid behavior     */
             r1 = q1; /* Valid behavior     */
             r1 = r2; /* Behavior undefined */
             q1 = r1; /* Behavior undefined */
             p1 = r1; /* Behavior undefined */
             ...
         }
     }

3.7.4.3.8 非限定ポインタへの代入

次の例のように,限定ポインタの値は,非限定ポインタに代入することができます。

     /* Assignments to unrestricted pointers */

     void f7(int n, float * __restrict r, float * __restrict s) {
         float * p = r, * q = s;
         while(n-- > 0)
             *p++ = *q++;
     }

Compaq Cコンパイラは,ポインタ値を追跡して,限定ポインタ rs が直接使用された場合と同様に, 効率的にループを最適化します。これは,この場合,pr に基づき,qs に基づいていると簡単に判断できるためです。

限定ポインタと非限定ポインタをより複雑に組み合わせて使用すると,難しすぎてコンパイラが分析できないため, あまり効率的ではありません。 パフォーマンスが重要な場合には,プログラミングのスタイルをコンパイラの機能に合わせる必要があります。 保守的な方法では,同じ関数内では, 限定ポインタと非限定ポインタの両方を使用しないようにします。

3.7.4.3.9 型修飾子の非効率的な使用

正式な定義で特別に記述している場合を除いて, __restrict 修飾子はconst および volatile と同様に動作します。

特に,関数の返却値の型またはキャストの型名を修飾することは制約違反ではありませんが, 関数呼出し式およびキャスト式は左辺値ではないため, 修飾子は何の影響も及ぼしません。

このため,次の例でf8 の宣言に__restrict 修飾子が指定されていても,f8 を呼び出す関数内のエイリアシングについては何の表明も行いません。

     /* Qualified function return type and casts */

     float * __restrict f8(void)  /* No assertion about aliasing. */
     {
         extern int i, *p, *q, *r;

         r = (int * __restrict)q; /* No assertion about aliasing. */

         for(i=0; i<100; i++)
             *(int * __restrict)p++ =   r[i];  /* No assertion    */
                                             /* about aliasing. */
         return p;
     }

同様に,2つのキャストは,ポインタp および rを介した参照のエイリアシングについて,何の表明も行いません。

3.7.4.3.10 制約違反

ポインタ型でないオブジェクト型を限定修飾することや,関数へのポインタを限定修飾することは, 制約違反です。次の例を参照してください。

     /*__restrict cannot qualify non-pointer object types: */

     int __restrict x;    /* Constraint violation */
     int __restrict *p;   /* Constraint violation */

     /* __restrict cannot qualify pointers to functions: */

     float (* __restrict f9)(void); /* Constraint violation */

3.8 型定義

typedef は型の同義語を定義するために使用します。 この定義では,識別子はオブジェクトの代わりに の名前を指定します。この定義方法によって,長い型定義や間違えやすい型定義に省略名を定義できます。

型定義では新しい基本データ型を作成しません。これは,基本型または派生型の別名を作成します。 たとえば,次のコードを使用すると,プログラムの後の部分で使用するオブジェクトのデータ型を説明するときに便利です。

     typedef float *floatp, (*float_func_p)();

この場合,floatp型は「float値へのポインタ」型であり, float_func_p型は「float を返す関数へのポインタ」型です。

型定義は,完全な型名が通常使用されている(通常の型名を使用できる)場所であれば, どこでも使用できます。型定義は変数と同じ名前空間を共有し, 定義した型はそれに等しい型と完全に互換性があります。修飾型として定義された型はその型修飾を引き継ぎます。

他の型定義から型定義を作成することもできます。次にその例を示します。

     typedef char byte;
     typedef byte ten_bytes[10];

型定義は変数または関数に適用できます。型定義を他の指定子と合わせて使用することはできません。 次にその例を示します。

     typedef int *int_p;
     typedef unsigned int *uint_p;
     unsigned int_p x;           /*  Invalid   */
     uint_p y;                   /*  Valid     */

型定義を使用して,関数型を宣言することもできます。ただし,型定義は関数定義に使用することはできません。 関数の返却値の型は型定義を使用して指定できます。 次にその例を示します。

     typedef unsigned *uint_p;   /* uint_p has type "pointer to unsigned int"    */
     uint_p xp;
     typedef uint_p func(void);  /* func has type "function returning pointer to */
                                 /* unsigned int                                 */
     func f;
     func b;
       func f(void)              /* Invalid -- this declaration specifies a      */
                                 /* function returning a function type, which    */
       {                         /* is not allowed                               */
         return xp;
       }

     uint_p b(void)              /* Legal -- this function returns a value of
       {                         /* type uint_p.                                 */
        return xp;
       }

次の例では,関数定義は typedef名から引き継ぐことができないことを示しています。

     typedef int func(int x);
     func f;
     func f         /*  Valid definition of f with type func                  */
     {
       return 3;
     }              /* Invalid, because the function's type is not inherited  */

この例を有効な形式に変更すると,次のようになります。

     typedef int func(int x);
     func f;
     int f(int x)   /*  Valid definition of f with type func           */
     {
       return 3;
     }              /* Legal, because the function's type is specified */

typedef名に仮引数名を含めて,プロトタイプ情報を取り込むことができます。 また,第2.3 節で説明したスコープ規則に従って,内部スコープに typedef名を再定義することもできます。