C言語は,オペレーティング・システムの記述に使用するために,最初は小規模で移植可能なプログラミング言語として設計されました。 その後現在に至るまでに, あらゆる種類のプログラムを記述するための強力なツールに成長を遂げました。C 言語は,最良のプログラミングを行うために次の機能を提供します。
この章では,C言語の機能を十分に活用するためのC言語の基本事項について説明します。
これらの各節で,C言語に出てくる用語と基本事項について説明します。 この基本事項を理解することによって,C言語を使用して作業を進めるための適切な基礎知識を得ることができ, 基本事項とC言語中のより複雑な事項との関係を理解する上で役に立ちます。
C言語のブロックとは,中括弧( { } )で囲まれたコード・ セクションを表します。ブロックの定義を理解することは, スコープ,可視性,外部宣言,および内部宣言といったC言語に出てくる他の多くの事項を理解するために非常に重要です。
次に,2つのブロックの例を示します。この例では,あるブロックが別のブロック中で定義されています。
main () { /* This brace marks the beginning of the first block */ int x; if (x!=0) { /* This brace marks the beginning of the second block */ x = x++; return x; }; /* This brace marks the end of the inner block */ } /* This brace marks the end of the outer block */
また,ブロックは複文でもあり,関連した一連のC言語の文を中括弧で囲んだ形で記述します。 プログラムに使用するオブジェクトの宣言は1 つのブロック内の任意の位置に指定でき,オブジェクトのスコープと可視性に影響を及ぼす可能性があります。 スコープについては第2.3節を, 可視性については第2.4 節を参照してください。
コンパイル単位は,コンパイルされ,1つの論理単位として扱われるC
ソース・コードです。コンパイル単位は通常,
1つまたは複数のファイルです。しかし,ファイルの一部を選択してそれをコンパイル単位にすることもできます。
たとえば,
#ifdef
前処理命令を使用して,特定のコード・セクションを選択することができます。
コンパイル単位内の宣言と定義は,関数とデータ・
オブジェクトのスコープを決定します。
#include
前処理命令を使用して取り込んだファイルは,コンパイル単位の一部となります。
条件付きの取込み前処理命令によって省かれたソース行は,
コンパイル単位には含まれません。
コンパイル単位は,識別子のスコープを決定し,他の内部識別子および外部識別子への識別子の結合を決定する上で重要な要素です。 スコープについては 第2.3節を,結合については第2.8節を参照してください。
コンパイル単位は,他のコンパイル単位のデータまたは関数を次の方法で参照することができます。
複数のコンパイル単位からなるプログラムをそれぞれ別々にコンパイルし, 後でリンクして,実行可能なプログラムを作成することができます。 有効なCコンパイル単位は,最低1つの外部宣言からなります。第4.3節を参照してください。
厳密なANSI規格モードを除くモードでは,宣言を持たない翻訳単位はコンパイラから警告が出されますが, 受け付けられます。
識別子のスコープとはプログラムの範囲であり,宣言された識別子はその範囲内で意味を持ちます。 識別子は,コンパイラによって認識された場合に意味を持ちます。 スコープは,識別子の宣言の位置によって決定されます。 スコープ外の識別子にアクセスしようとすると,エラーが生じます。 各宣言は,次の4種類のスコープのいずれかを持っています。
列挙定数のスコープは,列挙子並びにある列挙子の定義で始まります。文ラベルのスコープには, 関数本体全体が含まれます。他の型の識別子のスコープは, その識別子の宣言にある識別子自体で始まります。識別子のスコープを理解した上で, これ以降の各項を参照してください。
宣言の位置が任意のブロックまたは関数の仮引数並びの外側にある識別子は,
ファイル・スコープを持っています。ファイル・スコープを持つ識別子は,
内部ブロック宣言によって隠されていない限り,識別子の宣言からコンパイル単位の終了までの間で参照できます。
次の例では,
off
識別子はファイル・スコープを持っています。
int off = 5; /* Declares (and defines) the integer identifier off. */ main () { int on; /* Declares the integer identifier on. */ on = off + 1; /* Uses off, declared outside the function block of main. This point of the program is still within the active scope of off. */ if (on<=100) { int off = 0;/* This declaration of off creates a new object that hides the former object of the same name. The scope of the new off lasts through the end of the if block. */ off = off + on; return off; } }
ブロック内または関数定義の仮引数並び内に現れる識別子は,ブロック・スコープを持ち, 内部ブロック宣言によって隠されていない限りブロック内で参照できます。
ブロック・スコープは識別子の宣言で始まり,右中括弧(
} )で終了します。次の例では,red
識別子はブロック・
スコープを持ち,blue
はファイル・
スコープを持っています。
int blue = 5; /* blue: file scope */ main () { int x = 0 , y = 0; /* x and y: block scope */ int red = 10; /* red: block scope */ x = red + blue; }
関数スコープを持つのは文ラベルのみです(第7章を参照してください)。
関数スコープを持つ識別子は,
その識別子が宣言されている関数の範囲内では固有です。
ラベル付きの文は goto
文の対象として使用され,
構文(コロン( : )と文が続くラベル)によって暗黙に宣言されます。
次にその例を示します。
int func1(int x, int y, int z) { label: x += (y + z); /* label has function scope */ if (x > 1) goto label; } int func2(int a, int b, int c) { if (a > 1) goto label; /* illegal jump to undefined label */ }
文ラベルについては,第7.1節を参照してください。
仮引数宣言の関数プロトタイプ並び内に現れる識別子は,関数プロトタイプ・ スコープを持っています。この識別子のスコープは識別子の宣言で始まり, 関数プロトタイプ宣言並びの終わりで終了します。次にその例を示します。
int students ( int david, int susan, int mary, int john );
この例では,識別子(david,susan,mary
,および
john
)は宣言で始まり右括弧で終了するスコープを持っています。
students
関数の型は,「4つの
int
仮引数を持つ int
を返す関数」です。これらの識別子は実際,関数が定義された後に使用される実際の仮引数名用のプレースホルダです。
識別子は,プログラムの一定の範囲内でのみ可視性があります。 識別子はネストされたブロック内で再度宣言することによって上書きされるか, または隠されることがない限りスコープ全体で可視性 があります。可視状態の場合にのみ識別子を使用できるため,可視性はデータ・ オブジェクトまたは他の識別子へのアクセス機能に影響を与えます。
識別子を特定の目的のために使用した後は,その識別子を同じスコープ内で別の目的のために再び使用することはできません。 ただし,異なる 名前空間で使用することはできます。名前空間の制限については, 第2.15節を参照してください。 たとえば, ある識別子と同じ名前の異なる2つのデータ・オブジェクトを同一のスコープ内で宣言することはできません。
同じ名前を持つ2つの識別子のうちの一方のスコープが,もう一方に含まれている( ネストされている)場合には,内側のスコープを持つ識別子は可視状態のままです。 これに対して,より広いスコープを持つ識別子は,識別子の内側のスコープが継続する間は隠された状態になります。
次の例では,number
識別子が2回使用されています。1
回は整数変数として,もう1回は浮動小数点変数として使用されています。
main
関数内では,number
整数は
number
浮動小数点によって隠されます。
#include <math.h> int number; /* number is declared as an integer variable */ main () { float x; float number; /* This declaration of number occurs in an inner block, and "hides" the outer declaration. The inner declaration creates a new object */ x = sqrt (number);/* x receives a floating-point value */ }
Compaq Cで有効な演算子のほとんどでは,式の評価順序は指定されていません。 評価順序は使用状況に応じてコンパイラ内で決定されるため,ある演算子を使用すると予測できない結果が生じる可能性があります。 この予測できない結果は, 副作用によって引き起こされます。
オペランドの記憶域に影響を与える演算子には,副作用があります。プログラマは, 必要な結果を得るために故意に副作用を引き起こすことができます。 実際に,代入演算子はジョブを行うための記憶域を変更したために発生した副作用です。Compaq C では,指定した式のすべての副作用がプログラム内の次の評価順序点までに完了することが保証されています。 評価順序点(シーケンス・ポイント) とはプログラム中にあるチェックポイントであり, ここで式の中の演算が終了しているかどうかをコンパイラが検査します。
最も重要な評価順序点は文の終了を示すセミコロンです。セミコロンに到達すると, すべての式とその副作用は完全に評価されます。その他の評価順序点を次に示します。
&&
expr2 (論理積演算子)
||
expr2 (論理和演算子)
上記の演算は評価順序,つまりシーケンスを保証します(この場合,
expr1
,expr2
,および
expr3
が式です)。各演算子について,
式 expr1
の評価は式
expr2
(条件式の場合には expr3
)
を評価する前に行われることが保証されます。
式の評価順序がわからない場合は,実行順序に依存する演算は行わないでください。 これは,結果に一貫性と移植性がなくなるからです。ある1つのデータ・ オブジェクトを同じ式の中で複数回使用する場合はもちろんですが,1 回しか使用しない場合でも副作用は発生します。たとえば,次のコードでは代入演算子のオペランドを評価する順序を定義していないため一貫性のない結果となります。
int x[4] = { 0, 0, 0, 0 }; int i = 1; x[i] = i++;
添字が評価される前に i
の増分が行われると,
x[2]
の値が1になります。添字が最初に評価されると,
x[1]
の値が1になります。
関数呼出しにも副作用があります。次の例では,
f1(y)
と f2(z)
の呼び出す順序を定義していません。
int y = 0; int z = 0; int x = 0; int f1(int s) { printf ("Now in f1\n"); y += 7; /* Storage of y affected */ return y; } int f2(int t) { printf ("Now in f2\n"); z += 3; /* Storage of z affected */ return z; } main () { x = f1(y) + f2(z); /* Undefined calling order */ }
x
の値は常に10になりますが,
printf
関数はどんな順序でも実行できます。
識別子は,最初に不完全型として宣言することができます。不完全型宣言はオブジェクトについて宣言しますが, そのサイズを決定するのに必要な情報は提供しません。 したがって,サイズのわからない配列の宣言は不完全型宣言です。
extern int x[];
不完全型は,次の宣言により完全にすることができます。不完全型は主に, 順方向参照の配列,構造体,および共用体に使用されます(順方向参照については, 第2.12節を参照してください) 。集成型のオブジェクトに不完全型のメンバを入れることはできません。 したがって,集成体オブジェクト(構造体または配列メンバ) にそれ自体を入れることはできません。これは,集成型はその宣言が終わるまで完了しないからです。 次の例では,どのように不完全構造体型が宣言され, そして完了するかを示しています。
struct s { struct t *pt }; /* Incomplete structure declaration */ . . . struct t { int a; float *ps }; /* Completion of structure t */
void
型は不完全型の特殊な型です。これは完了できない不完全型であり,
関数が値を返さないことを表すために使用します。
void
型については,第3.5
節を参照してください。
2つの型の互換性は,それぞれの型の類似点に依存します。型の互換性は, 型の変換および演算を行う際に特に認識すべき重要なポイントです。 同じオブジェクトまたは関数を参照する同じスコープにある有効な宣言は, すべて互換型を持たなければなりません。これら2つの型が次のカテゴリのいずれかにあてはまる場合, それらには互換性があります。
short
,signed
short
,short int
,および
signed short int
型は同じ。
unsigned short
および
unsigned short int
型は同じ。
int
,signed
,および
signed int
型は同じ。
unsigned
および
unsigned int
型は同じ。
long
,signed long
,long
int
,および signed long int
型は同じ。
unsigned long
および
unsigned long int
型は同じ。
signed int
とも互換性がある。
int tree()
など)を使用して宣言された関数型は,
その返却値の型に互換性がある場合には別の関数型と互換性がある。
int tree (int
x)
など)を使用して宣言された関数型は,次に示す場合には関数プロトタイプで宣言される別の関数型と互換性がある。
tree
関数と
tree2
関数には互換性があるが,
tree
関数と tree1
関数,および tree1
関数と
tree2
関数には互換性はない。
int tree (int); int tree1 (char); int tree2 (x) char x; /* char promotes to int in old-style function parameters, and so is compatible with tree */ { ... };
次の型は,互換性があるように見えますが互換性はありません。
unsigned int
および
int
型
char
,signed char
,および
unsigned char
型
合成型は2つの互換型から構成され,2つの型の両方に互換性があります。 合成型は次の条件を満たします。
次のファイル・スコープの宣言を例にとります。
int f(int (*) (), double (*) [3]); int f(int (*) (char *), double (*)[]);
この関数の合成型は次の結果になります。
int f(int (*) (char *), double (*)[3]);
前述の合成型規則は,合成型から派生した型に再帰的に適用されます。
データ・オブジェクトおよび関数には,暗黙または明示的に結合 を割り当てることができます。結合には次の3種類があります。
同じコンパイル単位で宣言され,そのコンパイル単位外では認識されないデータ・ オブジェクトまたは関数を参照する宣言。
コンパイル単位外で認識されているデータ・オブジェクトまたは関数の定義を参照する宣言。 そのオブジェクトの定義も外部結合を持つ。
固有のデータ・オブジェクトを宣言する宣言。
同じオブジェクトまたは関数の宣言が複数作成されると,結合が作成されます。 結合した宣言は,同じスコープにも異なるスコープにも入れることができます。 外部結合されたオブジェクトは,実行可能なファイルの作成に使用される任意のコンパイル単位の任意の関数で使用可能です。 内部結合されたオブジェクトは, 宣言が現れるコンパイル単位でのみ使用できます。
static
および
extern
キーワードと結合の概念には関連がありますが,
直接の関連はありません。オブジェクトの宣言に
extern
キーワードを使用しても,外部結合は保証されません。
次の規則は,オブジェクトまたは関数の実際の結合を決定します。
auto
または
register
記憶域クラスで明示的に指定された識別子は結合を持たない。
extern
記憶域クラス指定を持つ識別子は,
ファイル・スコープを持つ同じ識別子の任意の可視宣言と結合を持つ。
オブジェクトまたは関数のこのような宣言が可視状態ではない場合,
そのオブジェクトまたは関数は外部結合を持つ。
static
である。static
は明示的に指定しなければならず,ブロック・スコープの関数宣言には適用できない。
このため,これは内部結合になる。
extern
記憶域クラス指定を持つデータ・オブジェクトのファイル・
スコープ宣言は外部結合を持つ。
static
記憶域クラスを持つ識別子は,
内部結合を持つ。
extern
記憶域クラス指定を持たない識別子は結合を持たない。
データ・オブジェクトと関数以外の識別子は結合を持ちません。また,関数の仮引数として宣言される識別子も結合を持ちません。
次の例は,異なる結合を持つ宣言を示しています。
extern int x; /* External linkage */ static int y; /* Internal linkage */ register int z; /* Illegal storage-class declaration */ main () /* Functions default to external linkage */ { int w; /* No linkage */ extern int x; /* External linkage */ extern int y; /* Internal linkage */ static int a; /* No linkage */ } void func1 (int arg1) /* arg1 has no linkage */ { }
Compaq Cでは同じオブジェクトが内部結合と外部結合の両方で宣言されると, メッセージが出されます。
ファイル・スコープを持つ識別子,初期化子を持たない識別子,記憶域クラス指定子,
または static
記憶域クラス指定子を持たない識別子の宣言は
仮定義と呼びます。オブジェクトの他の定義がコンパイル単位に現れない場合には,
仮定義のみが適用されます。
この場合,オブジェクトのすべての仮定義はオブジェクトにファイル・
スコープ定義が1つだけ存在する場合と同様に扱われ,0で初期化されます。
仮定義されたオブジェクトの定義を後でコンパイル単位に使用すると, その仮定義はオブジェクトの冗長な宣言として扱われます。オブジェクトの識別子の宣言が仮定義であり, 内部結合を持つ場合には,宣言された型は不完全型にすることはできません。 結合については,第2.8節を参照してください。
次に,仮定義の例を示します。
int i1 = 1; /* Standard definition with external linkage */ int i4; /* Tentative definition with external linkage */ static int i5; /* Tentative definition with internal linkage */ int i1; /* Valid tentative definition, refers to previous */ /* i1 declaration */
記憶域クラスは,データ・オブジェクトと関数の仮引数のみに適用されます。 ただし,Compaq Cの記憶域クラス・キーワードは関数の可視性に影響を与えるためにも使用します。 プログラムで使用される各データ・オブジェクトと仮引数は, それぞれ1つだけ記憶域クラスを持ちます。記憶域クラスは明示的にも, 特に指定しない場合でも割り当てられます。記憶域クラスには次の4 種類があります。
オブジェクトの記憶域クラスは,リンカに対する有効性とその記憶域存続期間
を決定します。外部結合または内部結合を持つオブジェクト,
あるいは static
記憶域クラス指定子を持つオブジェクトは,
静的記憶域存続期間を持ちます。この期間は,
main
が実行を開始する前にオブジェクトの記憶域が確保され,1
回だけ0に初期化されます。結合および
static
記憶域クラス指定子を持たないオブジェクトは,自動記憶域存続期間を持ちます。
このオブジェクトの場合には,記憶域はそれが宣言されたブロックに入ると自動的に割り当てられ,
ブロックから出ると自動的に割当てが解除されます。
自動オブジェクトは初期化されません。
関数に適用する場合には,extern
記憶域クラス指定子によって,
関数を他のコンパイル単位から可視状態にします。また,
static
記憶域クラス指定子によって,関数を同じコンパイル単位内の別の関数に対してのみ可視状態にします。
次にその例を示します。
static int tree(void);
これ以降の項では,それぞれの記憶域クラスについて説明します。
auto
クラスは,オブジェクトを定義するブロックの開始時にオブジェクトの記憶域を作成し,
ブロックの終了時に破壊するように指定します。
このクラスは,ブロック(例:関数本体)の始まりでのみ宣言することができます。
次にその例を示します。
auto int a; /* Illegal -- auto must be within a block */ main () { auto int b; /* Valid auto declaration */ for (b = 0; b < 10; b++) { auto int a = b + a; /* Valid inner block declaration */ } }
auto
オブジェクト(第4.2
節を参照のこと)を持つ初期化子を使用すると,そのオブジェクトは作成するたびに初期化されます。
ブロックの通常処理またはブロックへの飛越し文のいずれかにより,
そのオブジェクトを含むブロックに入ってくると,
記憶域はそのオブジェクト用に確保されます。ただし,飛越し文でブロックに入ると,
そのオブジェクトが初期化されない場合があります。
オブジェクトが可変長配列である場合,記憶域は予約されません。
auto
クラスは,ブロック・スコープを持つオブジェクトの省略時の設定です。
auto
クラスを持つオブジェクトは,リンカで使用できません。
register
クラスは割り当てられたオブジェクトの使用頻度が高いかどうかを識別し,
高い場合にはアクセス時間を最小にするためにそのオブジェクトをレジスタに割り当てるようにコンパイラに通知します。
register
は省略時のクラスとしては設定できず,
明示的に指定しなければなりません。
register
クラスは auto
クラスと同じ記憶域存続期間を持ちます。
つまり,記憶域はオブジェクトを定義するブロックの開始時に
register
オブジェクト用に作成され,
そのブロックの終了時に破壊されます。
register
クラスは,関数の仮引数に明示的に指定できる唯一の記憶域クラスです。
Compaq Cコンパイラは高度なレジスタ割当て技法を使用しているため,
register
キーワードは必要ありません。
static
クラスは,そのプログラムの開始から終了までの間,
識別子のための領域を保存するように指定します。静的オブジェクトはリンカには使用できません。
したがって,別のコンパイル単位に異なるオブジェクトを参照する同一の宣言を含めることができます。
静的オブジェクトは,プログラム中で宣言を指定できる場所であればどこにでも宣言することができます。
つまり,auto
クラスのようにブロックの開始時に宣言する必要はありません。
データ・オブジェクトを関数外で宣言すると,
省略時の静的記憶域存続期間が与えられ,プログラムの開始時に1
回だけ初期化されます。
static
オブジェクトを初期化するために使用する式は,定数式でなければなりません。
static
記憶域存続期間を持つオブジェクトが明示的に初期化されない場合には,
そのオブジェクトの各算術メンバは0
に初期化され,各ポインタ・メンバは空ポインタ定数に初期化されます。
さまざまなデータ型のオブジェクトを初期化する方法については,
第4.2節を参照してください。
extern
クラスは,ファイル・スコープを持つオブジェクトの省略時のクラスです。
関数の外にあるオブジェクト(外部定義)は,宣言で明示的に
static
キーワードが割り当てられない限り
extern
クラス記憶域を持ちます。extern
クラスは
static
オブジェクトと同じ記憶域存続期間を指定しますが,
オブジェクト名または関数名はリンカからは隠されません。
宣言に extern
キーワードを使用するとほとんどの場合に外部結合となり(
第2.8節を参照のこと)
,オブジェクトは静的記憶域存続期間を持ちます。
Compaq Cには,次のような記憶域クラス修飾子があります。
__inline
__forceinline
__align
inline
リストの上から3番目までは,
すべてのプラットフォームのすべてコンパイラ・モードで,
有効なキーワードとして識別されます。
これらは,Cの処理系用に予約されたネームスペースにあり,
そのためこれらをユーザ宣言識別子として扱う必要はありません。
これらはすべてのプラットフォームで同一の効果を持ちますが,
OpenVMS VAXシステムでは,
__forceinline
修飾子は
__inline
修飾子以上のインライン処理を行いません。
inline
記憶域クラス修飾子は,relaxed ANSI Cモードの場合か,
/ACCEPT=C99_KEYWORDS (OpenVMS)または
/ACCEPT=GCCINLINE (OpenVMS)修飾子を指定した場合にサポートされます。
noshare
,readonly
,
_align
のサポートも行っています。
これらの記憶域クラス修飾子についての詳細は,『Compaq C for OpenVMSシステム ユーザーズ・ガイド』
(OpenVMS)を参照してください。
記憶域クラス指定子および記憶域クラス修飾子は,任意の順序で使用できます。 通常,ソース・コードでは修飾子は指定子の後に置きます。 次の例を参照してください。
extern noshare int x; /* Or, equivalently . . . */ int noshare extern x;
ただし,記憶域クラス指定子を最初以外の任意の位置に置く方式は 使用されなくなってきています。
以降の各項では,各Compaq C 記憶域クラス修飾子について説明します。
__inline記憶域クラス修飾子は,インライン拡張用の関数を指定します。 関数定義およびプロトタイプに__inlineを使用することにより, コンパイラがその関数の呼出し毎にその関数定義内のコードを置換するように指定します。 置換はコンパイラの判断に応じて発生します。__inline 記憶域クラス修飾子は,#pragma inlineプリプロセッサ指示文と同一の効果を持ちます。 ただし,#pragma inlineは,選択された関数だけでなく, 変換ユニット内のすべての関数についてインライン拡張を提供しようとします(#pragma inline についての詳細は,プラットフォーム固有の Compaq Cドキュメントを参照してください) 。
関数にインライン拡張を割り当てる場合は,次の書式を使用します。
__inline [型] function_definition
/STANDARD=PORTABLEモードで__inlineが使用された場合,これは処理系固有の拡張であるため, コンパイラは警告を通知します。
次は,__inlineを使用する例です。
/* prototype */ __inline int x (float y); /* definition */ __inline int x (float y) { return (1.0); }
__inline
記憶域クラス修飾子と同じように,
inline
記憶域クラス修飾子は
関数宣言の宣言指定子として使用できます。
inline
記憶域クラス修飾子は,relaxed ANSI Cモードの場合か,
/ACCEPT=C99_KEYWORDS (OpenVMS)または
/ACCEPT=GCCINLINE (OpenVMS)修飾子が指定された場合に
サポートされます。
静的関数では,inline
を使用すると,
__inline
または#pragma inline
を関数へ適用した場合と同じ効果があります。
ただし,外部結合の関数にinline
を適用した場合,
その翻訳単位内の呼出しがインライン化されるのに加えて,
inline
の意味に,
他の翻訳単位内でもその関数への呼出しがインライン化されるか,
その関数は外部関数として呼び出されるかはコンパイラが判断できるという規則が追加されました。
inline
キーワードが
外部結合の関数宣言で使用された場合,
その関数は同じ翻訳単位内でも定義されていなければならない。
inline
キーワードを使用し,
extern
キーワードを使用しない場合,
その翻訳単位内の定義はインライン補助定義と呼ばれ,
外部呼出しが可能な(グローバル)定義はそのコンパイル単位内では生成されない。
上記の条件以外の場合,外部呼出しが可能な定義が,コンパイル単位内で生成される。
inline
補助定義は,
静的記憶域存続期間を持つ,変更可能なオブジェクトの定義を含んではならない。
また,内部結合の識別子を参照してはならない。
これらの制限は,外部呼出しが可能な定義には適用されない。
inline
修飾子の有無にかかわらず,
外部関数への呼出しに翻訳されることがある。
この事項と前項から,外部結合の関数が呼び出される場合,
その関数の外部呼出し可能な定義は,
プログラム全体のすべてのコンパイル単位の中で1つだけでなければならない。
inline
関数のアドレスは,
一意に定まる外部呼出し可能な定義のアドレスとして必ず算出され,
インライン定義のアドレスになることはない。
inline
関数呼出しは,
インラインのままであることも,
インライン定義への呼出しとして翻訳されることもある。
inline
キーワードがない場合,
ヘッダ・ファイルが複数の翻訳単位にインクルードされていれば,
ヘッダ・ファイル内の関数定義はリンク時にMULDEFエラーになる。
このような関数定義では,MULDEFエラーを回避する方法の1つとして
inline
を指定することができる。
例(第2.11.2.1項)を参照。
ここでは,C99規格の inline
キーワードの意味について
説明します。
gccコンパイラでは,外部結合関数用として,
このC99インライン機能と同じような機能を備えたインライン関数宣言指定子
を実装していますが,詳細な使用方法はやや異なっています。
特に,extern
キーワードなしでinline
キーワードを使用するのではなく,
extern
キーワードとinline
キーワード
を組み合わせるとインライン定義になります。
/ACCEPT=[NO]GCCINLINE修飾子は, 機能のどのバリエーションが実装されているかを制御します。
次のようなCコードがあるとします。
このコードでは,関数識別子(my_max
)が多重定義になります。
$ type t.h int my_max (int x, int y) { if (x >= y) return (x); else return (y); } $ $ type a.c #include "t.h" main() { int a =1; int b=2; func1(); my_max(func1(a,b),20); } $ $ type b.c #include "t.h" void func1(int p1, int p2) { my_max(p1,p2); } $ $ link a,b %LINK-W-MULDEF, symbol MY_MAX multiply defined in module B file DISK$:[TEST.TMP]B.OBJ;4
この問題を回避する方法の1つとして,my_max
関数を
static
キーワードで定義する方法があります。
static int my_max (int x, int y) { if (x >= y) return (x); else return (y); }
ただしこの場合,
グローバルに認識されるmy_max
関数があるのではなく,
各モジュールにmy_max
のコピーがあり,
コピーのアドレスはそれぞれ異なっています。
このため,関数ポインタの比較はうまくいきません。
ISO C99では,この問題をinline
キーワードで解決しています。
次のようにinline
をヘッダ・ファイルt.h
に
追加すると,MULDEFエラーを回避できます。
inline int my_max (int x, int y) { if (x >= y) return (x); else return (y); }
このタイプの関数定義は,
__inline
キーワードを指定した場合と同じように,
コンパイラによってインライン化される可能性があることを示します。
異なるのは,inline
関数ではコンパイラが,
宣言される関数(この例ではmy_max
)に対応する,
関数のインライン補助定義を作成するという点です。
ここで,コンパイラは次のいずれかを行います。
my_max
)を呼び出す。
この場合,アプリケーションのいずれかのモジュールに,
非static
inline
関数のグローバル定義が
必ずなければならない。
my_max
への呼出しのインライン・コードを生成する。
1つのinline
関数に対して作成できるグローバル定義は,
アプリケーション内で1つだけです。
インライン補助定義はモジュールごとに1つ,または補助関数のプロトタイプ宣言をモジュールごとに複数作成できます。
モジュールのいずれか(この例ではa.c
)に,
次のようにファイル・スコープ関数宣言をインクルードすることで,
グローバル・インライン定義を作成できます。
inline
キーワードを除去する。
#include "t.h" int my_max (int x, int y);
または
#include "t.h" extern int my_max (int x, int y);
inline
キーワードに
extern
記憶域クラスを指定する。
#include "t.h" extern inline int my_max (int x, int y);
inline
関数のアドレスを取得すると,
補助関数ではなく,必ずグローバル関数のアドレスとなります。
__inline記憶域クラス修飾子と同様,__forceinline記憶域クラス修飾子は関数にインライン拡張を指定します。 ただし,関数定義およびプロトタイプに__forceinline を使用した場合は,その関数へのすべての呼出しに対して関数定義内のコードを置換しなければならないことをコンパイラに指示します(__inline を使用した場合は,置換はコンパイラの自由な判断に応じて発生します) 。
OpenVMS VAXシステムでは,__forceinline記憶域クラス修飾子は__inline 修飾子が行う以上のインライン処理を発生させません。
関数に強制的なインライン拡張を指定する場合は,次の書式を使用します。
__forceinline [型] function_definition
/STANDARD=PORTABLEモードで__forceinlineが使用された場合,これは処理系固有の拡張であるため, コンパイラは警告を通知します。
__align
と_align
記憶域クラス修飾子の意味は,
同じです。
異なるのは,
__align
はすべてのコンパイラ・モードで使用できるキーワードで,
_align
はVAX Cキーワードを認識するモードのみで使用できるキーワードであるという点です。
新しいプログラムでは,__align
を使用してください。
__align
記憶域クラス修飾子は,
Compaq Cデータ型のオブジェクトを,指定された記憶域境界上に調整します。
データ宣言やデータ定義では,
__align
修飾子を使用してください。
たとえば,整数を次クォドワード境界上に調整するには, 次の宣言のいずれかを使用します。
int __align( QUADWORD ) data; int __align( quadword ) data; int __align( 3 ) data;
データの境界調整の位置を指定するときは,定義済み定数を使用するか,
2の累乗を示す整数値を指定することができます。
これらの定数や,明示的な2の累乗は,
データを境界調整するときに埋め込むバイト数をCompaq Cに指示します。
前の例では,
int __align ( 3 )
で
23バイト(8バイト,クォドワード・メモリ)の境界調整を
指定しています。
表2-1では,すべての定義済み境界調整定数と それに対応する2の累乗数,バイト数を示しています。 OpenVMS VAXシステムの場合は,定数0,1,2,3,4, または9を指定することができます。 OpenVMS Alphaシステムの場合は, 0〜16の任意の定数を指定できます。
定数 | 2の累乗数 | バイト数 | |
---|---|---|---|
BYTE またはbyte | 0 | 1 | |
WORD またはword | 1 | 2 | |
LONGWORD またはlongword | 2 | 4 | |
QUADWORD またはquadword | 3 | 8 | |
OCTAWORD またはoctaword | 4 | 16 | |
5 (Alpha 専用) | 32 | ||
6 (Alpha 専用) | 64 | ||
7 (Alpha 専用) | 128 | ||
8 (Alpha 専用) | 256 | ||
9 | 512 | ||
10 (Alpha 専用) | 1024 | ||
11 (Alpha 専用) | 2048 | ||
12 (Alpha 専用) | 4096 | ||
13 (Alpha 専用) | 8192 | ||
14 (Alpha 専用) | 16384 | ||
15 (Alpha 専用) | 32768 | ||
PAGE またはpage | 16 (Alpha 専用) 9 (VAX 専用) | 65,536 (Alpha 専用) 512 (VAX 専用) |
識別子はいったん宣言しておくと自由に使用できます。識別子を宣言の前に使用するとその宣言は 順方向参照と呼ばれ,次の場合以外にはエラーになります。
goto
文が文ラベルを参照する。
次に,順方向参照の有効な例と無効な例を示します。
int a; main () { int b = c; /* Forward reference to c -- illegal */ int c = 10; glop x = 1; /* Forward reference to glop type -- illegal */ typedef int glop; goto test; /* Forward reference to statement label -- legal */ test: if (a > 0 ) b = TRUE; }
次の例では,順方向参照で構造体タグを使用しています。
struct s { struct t *pt }; /* Forward reference to structure t */ . /* (Note that the reference is preceded */ . /* by the struct keyword to resolve */ . /* potential ambiguity) */ struct t { struct s *ps };
タグは,プログラム内にある構造体,共用体,列挙型を参照する手段としてそれらと一緒に使用することができます。 構造体,共用体, または列挙型の宣言にタグを含めると,その宣言が参照できるところにある場合はそれらを指定できます。
以下に,構造体タグ,共用体タグ,列挙型タグの使用例を示します。
struct tnode { /* Initial declaration -- */ /* tnode is the structure tag */ int count; struct tnode *left, *right; /* tnode's members referring to tnode */ union datanode *p; /* forward reference to union type is declared below */ }; union datanode { /* Initial declaration -- */ /* datanode is the union tag */ int ival; float fval; char *cval; } q = {5}; enum color { red, blue, green };/* Initial declaration -- */ . /* color is the enumeration tag */ . . struct tnode x; /* tnode tag is used to declare x */ enum color z = blue; /* color tag declares z to be of type color; z is also initialized to blue */
この例で示すように,いったんタグを宣言しておくとオブジェクトを再定義しなくてもそのタグを使用して同じスコープ内の他の構造体, 共用体, または列挙型の宣言を参照できます。
構造体または共用体を完全に宣言する前にタグを宣言した場合には,不完全型を作成することができます。
不完全型はオブジェクト・サイズを指定しません。
したがって,不完全型を生成するタグは,オブジェクト・サイズが不要の場合にのみ使用できます。
この型を完了するには,同じスコープ内でそのタグを別に宣言することでオブジェクトを完全に定義しなければなりません。
次の例では,s
構造体型の不完全な宣言を後続の定義がどのように完了するかを示しています。
struct s; /* Tag s used in incomplete type declaration */ struct t { struct s *p; }; struct s { int i; };/* struct s definition completed */
不完全型の概念については,第2.6 節で説明しています。
次の宣言を例にとります。
struct tag; union tag;
この宣言は構造体型または共用体型を指定し,宣言のスコープ内でのみ見えるタグを宣言します。 宣言は,スコープがあればその範囲内に同じタグを持つ他の型とは異なる新しい型を指定します。
以下に,相互に参照する2つの構造体を指定する場合の最初のタグ宣言を示します。
struct s1 { struct s2 *s2p; /*...*/ }; /* D1 */ struct s2 { struct s1 *s1p; /*...*/ }; /* D2 */
s2
がネストのスコープ内のタグとして宣言された場合には,
D1
宣言は s2
を参照し,D2
に宣言された
s2
タグは参照しません。この文脈依存をなくすために,
D1
の前に次の宣言を挿入できます。
struct s2;
これは,内側のスコープ内で新しい s2
タグを宣言します。
その後,D2
が型の指定を完了します。
右辺値は2,x+3,または(x + y) * (a - b)などの式の値です。 右辺値は記憶域には割り当てられません。次のコードは,右辺値が0 と1の場合の例を示します。
if (x > 0) { y += 1; } x = *y; /* The value pointed to by y is assigned to x */
x
および y
識別子は,
割り当てられた記憶域を持つオブジェクトです。y
へのポインタは左辺値を保持します。
左辺値は,そのプログラムで使用されるオブジェクトの記憶位置を示す式です。 オブジェクトの位置は左辺値に,そこに記憶させる値は右辺値に示されます。 次の演算子は,常に左辺値を生成します。
[] * ->
ドット演算子( . )は通常,左辺値を生成します。
しかし,必ずしも左辺値を生成するわけではありません。たとえば,
f().m
は左辺値ではありません。
変更可能な左辺値とは,配列型,不完全型,またはconst修飾型を持たない左辺値を示します。 構造体か共用体の場合には,const修飾型のメンバを持たない左辺値を示します。
名前空間とは,識別子をプログラム内で使用したときの状況に基づいて行われる識別子分類を示します。 名前空間により,同一の識別子がオブジェクト, 文ラベル,構造体タグ,共用体メンバ,および列挙定数と同時に使用できるようになります。2 つの異なる要素に同じスコープの識別子を同時に明瞭に使用することは, 同一の識別子が異なる名前空間にある場合にのみ可能です。 識別子の使用状況によって,同じ名前の要素が複数あるために起こる不明瞭さが解決されます。
名前空間には,次の4種類があります。
たとえば,flower
識別子を1つのブロック内で使用して変数および列挙型タグの両方を表すことができます。
これは,変数およびタグが異なる名前空間にあるためです。
その結果,内部ブロックは
flower
列挙型タグに影響を与えずに,
flower
変数を再定義できます。したがって,いろいろな目的のために同じ識別子を使用する場合には,
その識別子を扱う名前空間およびスコープの規則を分析する必要があります。
第2.3節では,スコープの規則について説明しています。
構造体,共用体,および列挙型メンバ名は,これらのオブジェクトに同時に共通して使用できます。 メンバの参照にこれらを使用することで,識別子の意味の不明瞭さが解決されます。 しかし,構造体,共用体,または列挙型タグは固有なものにしなければなりません。 これは,この3つのオブジェクト型のタグが同じ名前空間を共有しているからです。
Compaq Cプログラムの翻訳には,複数の段階があります。コンパイラを起動すると通常, 実際のコンパイラ起動の前に以下の事象が発生します。
#include
前処理命令に指定されたファイルは,
これまでの4つの手順を繰り返して処理される。
前述の4番目の手順は前処理と呼ばれ,コンパイラの別の単位で処理されます。 各前処理命令は #記号で始まる行で使用されます。# 記号の前に空白が入ることもあります。この行は,Compaq Cソース・ファイルの他の部分とは構文上独立しており, ソース・ファイルのどこにでも記述することができます。 前処理命令の行は,論理行の終わりで終了します。
実際にプログラムをコンパイルせずに,ソース・ファイルの前処理を行うことができます( 使用可能なコンパイラ・オプションについては,プラットフォームに固有のCompaq C のマニュアルを参照してください)。前処理命令については, 第8章で説明しています。
場合によっては識別子なしで型名を指定できるものや,識別子なしで型名を指定しなければならないものがあります。 たとえば,関数のプロトタイプ宣言において関数の仮引数は型名でのみ宣言できます。 また,ある型から別の型にオブジェクトをキャストする場合には, 関連する識別子のない型名が必要です( キャストについては第6.4.6 項を,関数プロトタイプについては第5.5 節を参照してください)。キャストは,型名を使用して行います。 型名とは,関数の宣言または識別子を省略した関数またはオブジェクトの宣言です。
表 2-2に,型名とそれに関連した型の例を示します。
構成 | 型名 |
---|---|
int | int |
int * | int
へのポインタ |
int *[3] | int への3つのポインタの配列 |
int (*)[3] | 3つの
int の配列へのポインタ |
int *() | int へのポインタを返す仮引数指定がない関数
|
int (*) (void) | int を返す仮引数がない関数へのポインタ
|
int (*const []) (unsigned int,...) | 未指定の数を持つ関数へのconstポインタの配列。
各関数は,unsigned int 型の1つの仮引数と未指定の数の仮引数を持ちint
を返す。 |
表 2-2では,抽象宣言子の例も示しています。
抽象宣言子とは識別子を持たない宣言子です。この例では,
int
型名に続く文字が抽象宣言子を形成しています。
*
,[]
,および( )
文字はすべて,
特定の識別子を指定せずに宣言子を示しています。