6 式と演算子

式とはCompaq Cの一連の演算子とオペランドからなり,値を生成するか, または副作用を生成するものです。最も単純な式は定数と変数名から構成され, 値は直接生成されます。その他の式は,演算子と部分式を組み合わせて値を生成します。 式は値と型の両方を持ちます。

特に指定しない限りは部分式の評価順序および副作用の発生順序はありません。 したがって,順序に依存するコーディングを行うと,予測できない結果となる可能性があります。

式の各オペランドは互換型を持たなければなりません。場合によっては, オペランドのデータ型に互換性を持たせるためにコンパイラが変換を行います。

この章では式と演算子について説明します。

6.1 1次式

単純な式は1次式と呼ばれ値を示します。1次式には,あらかじめ宣言された識別子, 定数,文字列リテラル,および括弧で囲んだ式が含まれます。

1次式の構文は次のとおりです。

1次式


      識別子
      定数
      文字列リテラル
      ( 式 )

これ以降の各項では1次式について説明します。

6.1.1 識別子

識別子は,オブジェクトまたは関数を指定するときに宣言される1次式です。

型が算術演算,構造体,共用体,またはポインタの場合,オブジェクトを指定する識別子は 左辺値です。配列名はその配列の最初の要素のアドレスに評価されます。 配列名は左辺値ですが,可変左辺値ではありません。

関数を指定する識別子は関数指名子と呼ばれます。関数指名子は関数のアドレスに評価されます。

6.1.2 定数

定数は1次式であり,その型は形式(整数型,文字型,浮動小数型,または列挙型) によって異なります(第1.9節を参照してください) 。定数を左辺値にすることはできません。

6.1.3 文字列リテラル

文字列リテラルは1次式であり,その型は形式(文字型または wchar_t型)によって異なります(第1.9節を参照してください)。 文字列リテラルは左辺値です。

6.1.4 括弧で囲んだ式

括弧内の式が持つ型と値は,括弧のない式が持つ型と値と同じです。式を括弧で区切ることによって, 演算子の優先順位を変更することができます。

6.2 C言語演算子

変数と定数は,C言語演算子と結合することでより複雑な式を作成することができます。 表 6-1はC言語演算子の一覧です。

表 6-1 C言語演算子

演算子 説明
() f() 関数呼出し
[] a[10] 配列の参照
-> s->a 構造体および共用体メンバの選択
. s.a 構造体および共用体メンバの選択
+ [単項] +a aの値
- [単項] -a aの負数
* [単項] *a アドレスa のオブジェクトの参照
& [単項] &a aのアドレス
~ ~a aの1の補数
++ [前置] ++a 増分後のa の値
++ [後置] a++ 増分前のaの値
--[前置] -a 減分後のaの値
--[後置] a- 減分前のa の値
sizeof sizeof (t1) t1型を持つオブジェクトのサイズのバイト数
sizeof sizeof e 式eの型を持つオブジェクトのサイズのバイト数
__typeof__ __typeof__ (t1) 型t1の型
__typeof__ __typeof__ (e) 式eの型
_Pragma _Pragma (string-literal) string-literalの非文字列化
+ [2項]
- [2項]
* [2項]
/
%
a + b
a - b
a * b
a / b
a % b
aとbの和
a引くbの差
aとbの積
a割るbの商
a割るbの余り
>>
<<
a >> b
a << b
aをbビットだけ右にシフトする
aをbビットだけ左にシフトする
<
>
<=
>=
==
!=
a < b
a > b
a <= b
a >= b
a == b
a != b
a < bの場合1,それ以外は0
a > bの場合1,それ以外は0
a <= bの場合1,それ以外は0
a >= bの場合1,それ以外は0
aがbに等しい場合1,それ以外は0
aがbと等しくない場合1,それ以外は0
& [2 項]
|
^
a & b
a | b
a ^ b
aとbのビットAND
aとbのビットOR
aとbのビットXOR (排他的OR)
&&
||
!
a && b
a || b
!a
aとbの論理積(0または1になる)
aとbの論理和(0または1になる)
aの否定(0または1になる)
?: a ? e1 : e2 aが0以外の場合式e1
aが0の場合式e2
=
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
,
a = b
a += b
a -= b
a *= b
a /= b
a %= b
a >>= b
a <<= b
a &= b
a |= b
a ^= b
e1,e2
bをaに代入
aとbの和(aに代入)
a引くbの差(aに代入)
aとbの積(aに代入)
a割るbの商(aに代入)
a割るbの余り(aに代入)
aをbビット右にシフトする(aに代入)
aをbビット左にシフトする(aに代入)
a AND b (aに代入)
a OR b (aに代入)
a XOR b (aに代入)
e2 (e1が最初に評価される)

C演算子は,次のように分類されます。

演算子の優先順位は,1つの式の中の項のグループ分けを決定します。 優先順位は,式を評価する方法に影響を与えます。演算子の中には, 他の演算子よりも優先順位の高いものがあります。たとえば,乗算演算子は加算演算子よりも優先順位が高くなります。

     x = 7 + 3 * 2;        /* x is assigned 13, not 20   */

この文は,次の文に等しくなります。

     x = 7 + ( 3 * 2 );

式に括弧を使用すると,省略時の優先順位が変わります。次にその例を示します。

     x = (7 + 3) * 2;  /*  (7 + 3) is evaluated first    */

括弧で囲まれていない式では,高い優先順位を持つ演算子は低い優先順位を持つ演算子よりも前に評価されます。 次にその例を示します。

     A + B * C

乗算演算子( * )は加算演算子( + )よりも優先順位が高いため,B C の識別子の乗算が最初に行われます。

表 6-2は,C言語演算子を評価するためにコンパイラが使用する優先順位を示しています。 最も高い優先順位の演算子を表の先頭に示し, 最も低い優先順位の演算子を表の最後に示します。

表 6-2 C言語演算子の優先順位

結合規則
後置 () [] -> . ++ - - 左から右
単項 + - ! ~ ++ - - (型) * & sizeof 右から左
乗除 * / % 左から右
加減 + - 左から右
シフト << >> 左から右
関係 < <= > >= 左から右
等価 == != 左から右
ビットAND & 左から右
ビットXOR ^ 左から右
ビットOR | 左から右
論理積 && 左から右
論理和 || 左から右
条件 ?: 右から左
代入 = += -= *= /= %= >>= <<= &= ^= |= 右から左
コンマ , 左から右

結合規則は優先順位と関連しているため,同じ優先順位を持つ演算子のグループ分けの際の不明瞭さが解消されます。 次の文はC言語の規則により a * b が最初に評価されます。

     y = a * b / c;

より複雑な式の場合,たとえば次の例では結合規則は b ? c : d を最初に評価するように指定します。

     a ? b ? c : d : e;

条件演算子の結合規則は,行の右から左に評価されます。また,代入演算子も右から左に評価されます。 次にその例を示します。

     int x = 0 , y = 5, z = 3;
     x = y = z;                  /*  x has the value 3, not 5    */

その他の演算子は左から右に評価されます。たとえば,2項の加算,減算, 乗算,除算演算子はすべて左から右の結合規則を持ちます。

結合規則は表 6-2に示した演算子の各列に適用され, 右から左に評価されるものと左から右に評価されるものとがあります。 括弧で囲まれていない式では,結合規則により同じ列にある各演算子の評価順序が決まります。 次にその例を示します。

     A * B % C

この式は乗除演算子( *,/,% )が左から右に評価されるため, 次のように評価されます。

     (A * B) % C

式の中の優先順位と結合規則を制御するために,常に括弧を使用できます。

6.3 後置演算子

後置式には,配列の参照,関数呼出し,構造体参照または共用体参照,後置インクリメント式と後置デクリメント式が含まれます。 後置式は,左から右への結合規則を持ちます。

後置式の構文は,次のとおりです。

後置式:


      配列の参照
      関数呼出し
      構造体および共用体メンバの参照
      後置インクリメント式
      後置デクリメント式

6.3.1 配列の参照

大括弧演算子( [ ] )は,配列要素を参照するために使用します。 配列の参照の構文は,次のとおりです。

配列の参照:


      後置式 [  ]

たとえば1次元配列の場合,配列内の特定要素は次に示すように参照することができます。

     int sample_array[10];  /* Array declaration; array has 10 elements */
     sample_array[0] = 180; /* Assign value to first array element      */

この例は,配列の最初の要素である sample_ array[0] に180の値を代入しています。C言語では,0 を起点とする配列の添字付けを使用することに注意してください。

2次元配列(厳密には,配列の配列)では,次に示すように配列内の特定要素を参照できます。

     int sample_array[10][5];  /* Array declaration; array has 50 elements */
     sample_array[9][4] = 180; /* Assign value to last array element       */

この例は,要素である sample_array[9][4] に180の値を代入しています。

概念上からは,多次元配列は配列の配列の配列の....という型になります。 したがって,配列の参照が完全に修飾されていない場合には, 指定されていない次元の最初の要素のアドレスを参照することになります。 次にその例を示します。

     int sample_array[10][5]; /* Array declaration                      */
     int *p1;                 /* Pointer declaration                    */

     p1 = sample_array[7];    /* Assigns address of subarray to pointer */

この例では,p1 には1次元の部分配列である sample_array[7] にある最初の要素のアドレスが入っています。 この例のように,部分的に修飾した配列を右辺値として使用することはできますが, 左辺値としては完全に修飾した配列の参照しか使用できません。 たとえば,Compaq Cでは次のような文は使用できません。 これは,この文が配列の2次元を省略しているからです。

     int sample_array[10][5]; /* Array declaration                      */

     sample_array[7] = 21;    /* Error                                  */

大括弧のない配列名の参照を使用して,次に示す文のように配列アドレスを関数に渡すことができます。

     funct(sample_array);

また,大括弧演算子を使用して,次に示すように通常のポインタ算術演算を実行することもできます。

     p1[intexp]

この場合,p1 はポインタであり, intexp は整数値の式です。式の結果は p1 が示す値になります。 この値は,アドレス指定したオブジェクト(配列要素)のバイト単位のサイズを intexp の値に掛けて増分された値です。 式 * (p1 + intexp) と式 p1[intexp] は,等しい式として定義されます。両方の式とも,同じメモリ位置を参照し同じ型を持ちます。 配列の添字付けは交換可能です。 すなわち,intexp[p1] p1[intexp] に等しいものです。添字付きの式は常に左辺値になります。

6.3.2 関数呼出し

関数呼出しの構文は,次のとおりです。

関数呼出し:


      後置式

実引数式並び(opt):


      代入式
      代入式並び(opt), 代入式

関数呼出しは関数指名子からなる後置式であり,後に括弧が続きます。 関数の仮引数並びの式の評価順序は定義されていませんが,実際の呼出しの前には評価順序点が存在します。 括弧内には実引数並びを(コンマで区切って) 含めることも,括弧内を空にすることもできます。呼び出される関数が宣言されていなければ, int を返す関数とみなされます。

配列または関数である実引数を渡すには,識別子を大括弧または括弧を付けずに実引数並びに指定してください。 コンパイラは,その配列または関数のアドレスを呼び出されるルーチンに渡します。 つまり,呼び出される関数に対応する仮引数はポインタとして宣言しなければなりません。

次の例では,func1 double を返す関数として宣言されています。 仮引数数と型は指定されていません。

     double func1();

その後,func1関数は次に示すように関数呼出しに使用できます。

     result = func1(c);
         または
     result = func1();

また,func1識別子は括弧を付けずに他の文脈で使用できます。 次に,別の関数呼出しの実引数として使用している例を示します。

     dispatch(func1);

この例では,func1関数のアドレスが dispatch関数に渡されます。識別子が「 . . . を返す関数」型として宣言された場合, その識別子が括弧のない実引数として渡されるときに通常, 「 . . . を返す関数のアドレス」に変換されます。唯一の例外は, 関数指名子が単項&演算子のオペランドである場合です。この場合, 変換は明示的に行われます。

また,関数へのポインタを逆参照することにより関数を呼び出すこともできます。 次の例では,pf double を返す関数へのポインタとして宣言され, func1関数のアドレスが代入されます。

     double (*pf)( );
        .
        .
        .
     pf = func1;

func1関数は,次に示すように呼び出すことができます。

     result = (*pf)();

この関数呼出しは有効ですが,次に示す形式の方が簡潔です。

     result = pf();

関数呼出しにおいて呼び出される関数を示す式の型がプロトタイプを含んでいない場合, 第6.11.3項で説明する汎整数拡張がそれぞれの適用可能な実引数で実行され, float型を持つ実引数は double に変換されます。これらを省略時の実引数の拡張 と呼びます。渡される実引数の数が仮引数の数と一致しない場合の動作結果は定義されていません。 また,関数がプロトタイプを含まない型で定義され, さらに拡張後の実引数の型が拡張後の仮引数型と互換性がない場合の動作結果も定義されていません。 関数がプロトタイプを含む型で定義され, さらに拡張後の実引数の型が仮引数型と互換性がないか, またはプロトタイプが(可変個の仮引数並びを示す)省略記号で終了した場合も同様に動作結果は定義されていません。

呼び出される関数を示す式がプロトタイプを含む型を持っている場合には, 渡される実引数はそれに対応する仮引数の型に暗黙に変換されます。 関数プロトタイプに省略記号が含まれていると,最後に宣言した仮引数の後で実引数型変換が停止します。 実引数が残っている場合には,省略時の実引数の拡張が実行されます。 呼び出される関数を示す式が示す型と互換性のない型で関数が定義された場合の動作結果は定義されていません。

その他の変換は,暗黙には行われません。特に実引数の数および型が,プロトタイプを含まない関数定義の仮引数の数および型と比較されることはありません。

再帰的関数呼出しは,直接的にも間接的にも他の関数の連鎖を通して許可されています。

6.3.3 構造体と共用体の参照

構造体または共用体メンバは,ドット演算子( . ) を使用して直接的に,または矢印演算子(->)を使用して間接的に参照することができます。

構造体および共用体の参照(つまり構成要素の選択)の構文は,次のとおりです。

構造体および共用体の参照:


      後置式 . 識別子
      後置式 -> 識別子

矢印演算子は常に左辺値を生成します。ドット演算子は,後置式が左辺値である場合に左辺値を生成します。

直接メンバ選択では,最初のオペランドは構造体または共用体を指定しなければなりません。 さらに,識別子がその構造体または共用体の宣言されたメンバを指名しなければなりません。

間接メンバ選択では,最初のオペランドは構造体または共用体のポインタでなければなりません。 さらに,識別子がその構造体または共用体の宣言されたメンバを指名しなければなりません。 矢印演算子はハイフン( - )と右不等号( > )で指定され,構造体または共用体メンバの参照を指定します。 式 E1->name は,定義上では厳密に(* E1).name と同じです。また, これは E2 が左辺値である場合, E2.name が(&E2)->name と同じであることも意味しています。

指名された構造体メンバは,完全に修飾しなければなりません。すなわち, そのメンバの前にピリオド,矢印,またはその両方で区切られた上位レベル・ メンバの一連の名前がなくてはなりません。式の値は構造体または共用体の指名されたメンバであり, その型がそのメンバの型になります。 構造体と共用体については,第3.4.4 項第3.4.5項を参照してください。

1つの例外を除き,値を共用体の1つのメンバに格納した後でその共用体の異なるメンバにアクセスすると, その結果は参照したメンバのデータ型と共用体内の境界調整によって異なります。

この例外とは,共用体の使用を簡単にするためのものです。共用体に共通の初期シーケンスを共有する複数の構造体が含まれるとき, 共用体がこれらの構造体の1 つを現在含んでいる場合には,それらの共通の初期部分を調べることができます。2 つの構造体に対応するメンバが1つまたは複数の初期メンバのシーケンスの互換型( ビット・フィールドでは同じ幅)を持つ場合には, その2つの構造体は共通の初期シーケンスを共有します。

6.3.4 後置インクリメント演算子と後置デクリメント演算子

C言語には,スカラ型のオブジェクトの増分や減分を行う2つの単項演算子があります。 後置インクリメントの構文は,次のとおりです。

後置インクリメント式:


      後置式 ++

後置デクリメントの構文は,次のとおりです。

後置デクリメント式:


      後置式 --

オペランドがポインタである場合を除き,インクリメント演算子である ++ はそのオペランドに1 を加算し,デクリメント演算子である - はそのオペランドから1を減算します。オペランドが Tへのポインタ型のポインタである場合,そのポインタは, sizeof で増分(または減分)されます。これにより, T型のオブジェクトの配列内の次の(または前の)要素が示されます。

++ - の両方を前置演算子(オペランドの前,つまり ++n ),または後置演算子(オペランドの後,つまり n++ )として使用できます。これら両方の場合とも n を増分します。式 ++n はその値が使用される前に n を増分し,n++ はその値が使用された後に n を増分します。

インクリメント演算子とデクリメント演算子の前置形式については,第6.4.3項で説明します。 この項では後置形式について説明します。

次の式を例にとります。

左辺値 ++



後置演算子++は,定数1をオペランドに加えてそのオペランドを変更します。 式の値は1だけ増分されたオペランドの値,あるいは増分される前のオペランドの古い値です。 次にその例を示します。

     int i, j;
     j = 5;
     j++;                   /* j = 6 (j incremented by 1) */
     i = j++;               /* i = 6, j = 7               */

インクリメント演算子とデクリメント演算子を使用する場合には,式の評価の順序に依存しないようにしてください。 次の不明瞭な式を例にとります。

     k = x[j] + j++;

x[j] j値は j の増分前に評価されるのか,または増分後に評価されるのかが指定されていません。 不明瞭さを避けるために, 変数の増分を別の文で行います。次にその例を示します。

     j++;
     k = x[j] + j;

++演算子と -演算子は,浮動小数点オブジェクトとともに使用することもできます。 この場合には,1.0ずつオブジェクトをスケーリングします。

6.4 単項演算子

単項式は,単項演算子を単一オペランドと組み合わせることによって形成されます。 すべての単項演算子の優先順位は等しく,右から左への結合規則を持ちます。 単項演算子には次の種類があります。

6.4.1 単項プラス演算子と単項マイナス演算子

次の式を例にとります。

   - 式

これはオペランドの符号の反転です。オペランドは算術型を持たなければならず, 汎整数拡張が適用されます。unsigned数の加算に関する逆元の場合には, unsigned 型の最大値に1を加えた値からその値を減算して計算されます。

単項プラス演算子は,次の式の値を返します。

   + 式

単項プラス演算子も単項マイナス演算子も,左辺値は生成しません。

6.4.2 否定

次の式を例にとります。

   ! 式

この結果は式の論理(ブール)否定になります。式の値が0である場合,その否定結果は1 になります。式の値が0以外の場合,その否定結果は0になります。 結果の型は int です。この式はスカラ型を持たなければなりません。

6.4.3 前置インクリメント演算子と前置デクリメント演算子

C言語には,スカラ・オブジェクトの増分や減分を行うための2つの単項演算子があります。 インクリメント演算子である ++ は,1をオペランドに加算します。 デクリメント演算子である - は,オペランドから1を減算します。++ - の両方とも,前置演算子( 変数の前,++n ),または後置演算子(変数の後, n++ )として使用できます。いずれの場合も n を増分します。式 ++n は,その値が使用される前に n を増分します。これに対して, n++ はその値が使用された後に n を増分します。

後置インクリメント演算子と後置デクリメント演算子については,第6.3.4項で説明しています。 ここでは, 前置形式について説明します。

次の式を例にとります。

   ++ 可変左辺値

この式の評価後,結果は増分された右辺値であり,それに対応する左辺値ではありません。 このため,この方法でインクリメント演算子とデクリメント演算子を使用する式は, 左辺値が必要な代入式の左側にその式自体を記述することはできません。

オペランドを整数または浮動小数点数として宣言すると,1 (または1.0) だけ増加または減少します。次に示すそれぞれのC言語の文の結果は同じです。

     i = i + 1;
     i++;
     ++i;
     i += 1;

次に,インクリメント演算子の後置形式と前置形式の相違の例を示します。

     int i, j;
     j = 5;
     i = ++j;                      /*  i = 6, j = 6  */
     i = j++;                      /*  i = 6, j = 7  */

オペランドがポインタである場合には,そのアドレスは整数値の1だけ増分されるのではなく, 示されるオブジェクトのデータ型に決められているサイズ単位で増分されます。 次にその例を示します。

     char *cp;
     int  *ip;
     ++cp;             /* Incremented by sizeof(char) */
     ++ip;             /* Incremented by sizeof(int)  */

次の式を例にとります。

   - - 可変左辺値

前置演算子である - は前置演算子である ++ と類似していますが,オペランドの値が減分される点が異なります。

インクリメント演算子とデクリメント演算子を使用する場合には,式の評価の順序に依存しないようにコーディングを行ってください。 次の不明瞭な式を例にとります。

     k = x[j] + ++j;

x[j] j値は j の増分前に評価されるのか,または増分後に評価されるのかが指定されていません。 不明瞭さを避けるために, 変数の増分を別の文で行います。次にその例を示します。

     ++j;
     k = x[j] + j;

6.4.4 アドレス演算子と間接参照

次の式を例にとります。

   & 左辺値

この式の結果は左辺値のアドレスになります。左辺値は関数指名子か, または修飾されない配列識別子を含むオブジェクトを指定する左辺値です。 register変数またはビット・フィールドは,左辺値にすることはできません。

次の式を例にとります。

     *pointer

式がアドレスを解決すると,間接参照演算子( * ) を使用してそのアドレスに格納されている値にアクセスできます。

* のオペランドが関数名または関数ポインタの場合には,その結果は関数指名子になります。* のオペランドがオブジェクトへのポインタの場合には, その結果はそのオブジェクトを指定する左辺値になります。 ポインタに無効な値(例: 0)を代入した場合の * 演算の結果は定義されていません。

間接参照演算子である * は常に左辺値を生成します。 アドレス演算子である & は左辺値を生成しません。

6.4.5 ビット否定

次の式を例にとります。

   ~ 式

この式の結果は,評価された式のビット否定(1の補数)になります。1のビットはそれぞれ0 のビットに変換され,逆に0のビットはそれぞれ1のビットに変換されます。 式は整数型を持たなければなりません。コンパイラは通常の算術変換を行います( 第6.11.1項を参照してください) 。

6.4.6 キャスト演算子

キャスト演算子は,スカラ・オペランドを指定のスカラ・データ型または void に強制的に変換します。この演算子は, 括弧で囲んだ型名を式の前に付けた構成です。次の式を例にとります。

   (型名) 

式の値は,指名されたデータ型に変換されます。これは,その式がその型の変数に代入された場合と同様に変換されます。 この式の型と値自体は変更されませんが, キャスト演算の実行中だけその値がキャスト型に変換されます。 型名の構文は,次のとおりです。

型名


      型指定子  抽象宣言子

単純な場合は,型指定子 char または double などのデータ型のキーワードであり, 抽象宣言子は空になります。次にその例を示します。

     (int)x;

型指定子は,enum指定子または typedef名にすることもできます。型指定子は,抽象宣言子 がポインタの場合にのみ,構造体または共用体にすることができます。 つまり,型名は構造体や共用体へのポインタにすることはできますが, 構造体と共用体はスカラ型ではないため,構造体や共用体にすることはできません。 次にその例を示します。

     (struct abc *)x   /* allowed     */

     (struct abc)x     /* not allowed */

キャスト演算子の抽象宣言子は識別子のない宣言子です。抽象宣言子の構文は, 次のとおりです。

抽象宣言子


      抽象宣言子
      * 抽象宣言子
      抽象宣言子
      抽象宣言子[定数式]

次の形式では,抽象宣言子は空にできません。

   (抽象宣言子)

抽象宣言子には,配列と関数を示す大括弧と括弧を含めることができます。 ただし,キャスト演算は式を強制的に配列,関数,構造体,または共用体に変換することはできません。 大括弧と括弧は,次の例に示すような演算に使用されます。 この例では,P1識別子を int 配列へのポインタにキャストします。

     (int (*)[10]) P1;

この種のキャスト演算は,P1 の内容を変更しません。 この演算により,コンパイラは P1 の値をこの配列へのポインタとみなします。この方法でポインタをキャストすると, 整数がポインタに加算されるときに起こるスケーリングを変更できます。

     int *ip;
     ((char *)ip) + 1;   /* Increments by 1 not by 4 */

キャスト演算子は,ポインタを含む次の変換に使用できます。

6.4.7 sizeof演算子

次に,sizeof演算子の構文を示します。

   sizeof 
   sizeof (型名)

型名は不完全型,関数型,またはビット・フィールドにすることはできません。 sizeof演算子は,コンパイル時の整数定数値を生成します。 はその型を推定するためにのみ検査されます。 つまり,式は完全には評価されません。たとえば, sizeof(i++) sizeof(i) に等しくなります。

sizeof演算の結果は,オペランドのバイト単位のサイズになります。1 番目の場合,sizeof の結果は式の型で決定されるサイズになります。2 番目の場合,結果は指名された型のオブジェクトのサイズになります。 sizeof の優先順位は他のほとんどの演算子よりも高いため, 式が演算子を含む場合には, その式を括弧に入れなければなりません。

型名の構文はキャスト演算子の構文と同じです。次にその例を示します。

     int  x;
     x = sizeof(char *);  /* assigns the size of a character pointer to x */

sizeof演算子の結果である size_ t型は符号なし整数型になります。Compaq Cでは,size_t unsigned int になります。

6.4.8 __typeof__演算子

式の型を参照するもう1つの方法は,__typeof__演算子です。 この機能により,gccコンパイラとの互換性が保たれます。

この演算子のキーワードの構文はsizeofに似ていますが, その構造は意味的に,typedefで定義された型名と同じように動作します。

__typeof__ (  )
__typeof__ ( 型名 ) 

__typeof__の引数には, 式と型名の2通りの記述方法があります。

式を使用する例を次に示します。 この例では,xintの配列であるとします。 記述される型は,intです。

__typeof__(x[0](1))

引数として型名を使用する例を次に示します。 記述される型は,intへのポインタ型です。

__typeof__(int *)

__typeof__構造は, typedef名が使用できるすべての場所で使用できます。 たとえば,宣言内,キャスト内,sizeofまたは __typeof__演算子の内部などで使用できます。

__typeof__(*x) y;     // Declares y with the type of what x points to.

__typeof__(*x) y[4];  // Declares y as an array of such values.

__typeof__(__typeof__(char *)[4]) y;  // Declares y as an array of 
                                      // pointers to characters:

最後の例(ネストした__typeof__演算子)は, 次のような従来のC宣言と等価です。

char *y[4];

__typeof__を使用した宣言の意味と, そのように記述することがなぜ便利なのかを理解するために, マクロを使用して書き直してみます。

#define pointer(T)  __typeof__(T *)
#define array(T, N) __typeof__(T [N])

宣言は,次のように書き直せます。

array (pointer (char), 4) y;

このため,(array (pointer (char), 4))は, charへのポインタが4つある配列の型になります。

6.4.9 _Pragma演算子

_Pragma演算子は, 引数の文字列リテラルを非文字列化し, マクロ展開で効果的に#pragma命令を生成します。 この演算子を使って指定したときには, プラグマのトークンがこの形式の単一の文字列リテラル内に一緒に指定され, _m 接尾語が付いていたとしてもマクロ展開はされません。 ただし,必要な場合は, 文字列化演算子(#)を使って文字列を形成することで, マクロ展開を実行させることができます(第8.1.3項を参照してください)。

_Pragma演算子の構文は,次のとおりです。

_Pragma ( string-literal )

_Pragma演算子の式は,次のように処理されます。 L接頭語があればそれを削除し, 先頭と末尾の二重引用符を削除し, エスケープ・シーケンス \" を二重引用符で置き換え, エスケープ・シーケンス \\ をバックスラッシュ1個に置き換えることで, 文字列リテラルが非文字列化されます。

結果として得られた文字シーケンスは,翻訳フェーズ3で処理され, プラグマ命令のpp-tokensのように実行される前処理トークン が生成されます。 単項演算子の式にある元の4つの前処理トークンは削除されます。

例: 次の形式のプラグマ命令があるとします。

#pragma listing on "..\listing.dir"

このプラグマ命令は,次のように表現することもできます。

_Pragma ( "listing on \"..\\listing.dir\"" )

後者の形式は,そのまま記述した場合も, 次のようなマクロ置換の結果として得られた場合も,同じ方法で処理されます。

#define LISTING(x) PRAGMA(listing on #x)
#define PRAGMA(x)  _Pragma(#x)

LISTING ( ..\listing.dir )

6.5 2項演算子

次に,2項演算子の種類を示します。

これ以降の項では,それぞれの2項演算子について説明します。

6.5.1 乗除演算子

乗除演算子は *,/,% です。オペランドは算術型を持たなければなりません。 必要に応じて,オペランドは通常の算術変換規則により変換されます( 第6.11.1項を参照してください) 。

* 演算子は乗算を行います。

/ 演算子は除算を行います。整数同士の除算の場合には,0に近い値に切り捨てられます。 いずれかのオペランドが負である場合には, その結果は0に近い値(代数の商よりも絶対値の小さい最大整数)に切り捨てられます。           

% 演算子は最初のオペランドを2番目のオペランドで割り,剰余を出します。 両方のオペランドは汎整数でなければなりません。両方のオペランドが符号なしの数または正の数である場合, その結果は正となります。 いずれかのオペランドが負である場合,結果の符号は左オペランドの符号と同じになります。

b が0以外の場合,次の文は真になります。

     (a/b)*b + a%b == a;

エラー検査オプションを指定した場合,Compaq Cコンパイラは定義されていない次の動作に対して警告を出します。

6.5.2 加減演算子

加減演算子+と-は,それぞれ加算と減算を行います。必要に応じて,通常の算術変換規則によりオペランドが変換されます( 第6.11.1項を参照してください)。

2つの enum定数または変数が加算または減算されると, その結果は int型になります。

整数がポインタ式に加算またはポインタ式から減算されると,その整数は示されるオブジェクトのサイズでスケーリングされます。 この結果はポインタの型を持ちます。 次にその例を示します。

     int arr[10];
     int *p = arr;
     p = p + 1;  /* Increments by sizeof(int) */

配列ポインタは,ポインタまたはアドレスから整数値を減算することにより減分できます。 同様の変換が加算にも適用されます。また,ポインタ演算は配列の終わりを超えた次の1 要素にも適用されます。たとえば,次のコーディングはポインタ演算が配列の要素とその配列を超えた次の1 要素までに限定されているため有効です。

     int i = 0;
     int x[5] = {0,1,2,3,4};
     int y[5];
     int *ptr = x;
     while (&y[i] != (ptr + 5)) { /*  ptr + 5 marks one beyond the end of the array */
       y[i] = x[i];
       i++;
     }

同じ配列の要素への2つのポインタ間で減算されると,その結果(2つのアドレス間の差を1 つの要素の長さで割って計算した結果)は ptrdiff_t型の値になります。これはCompaq Cでは int型になり, アドレス指定された2つの要素間の要素の数を表します。2つの要素が同じ配列にない場合の演算結果は定義されていません。

6.5.3 シフト演算子

シフト演算子である << >> は,右オペランドで指定したビット数だけ左オペランドを左( << )または右( >> )にそれぞれシフトします。 両方のオペランドは,整数でなければなりません。 コンパイラは,各オペランドに対して汎整数拡張を行います(第6.11.1.1項を参照してください)。 その結果の型は,拡張された左オペランドの型になります。次の式を例にとります。


     E1 << E2

この式の結果は,E2ビットだけ左にシフトされた式 E1 の値になります。シフトにより左端から押し出されたビットは破棄されます。 空きになった右のビットには0が埋め込まれます。 左にシフトすることは,シフトする各ビットごとに左オペランドに2 を掛けることになります。次の例では,i の値は100 になります。

     int n = 25;
     int m = 2;
     int i;

     i = n << m;

次の式を例にとります。


     E1 >> E2

この式の結果は,E2ビットだけ右にシフトされた式 E1 の値になります。シフトにより右端から押し出されたビットは破棄されます。 E1 unsigned であるか,または E1 が符号付きの型であるが負以外の値を持っている場合には, 空きになった左のビットには0が埋め込まれます。E1 が符号付きの型で負の値を持っている場合には,空きになったビットには1 が埋め込まれます。

右オペランドが負の場合,またはその値が int のビット数よりも大きい場合のシフト演算結果は定義されていません。

左オペランドが負以外の場合,右にシフトすることはシフトする各ビットごとに左オペランドを2 で割ることになります。次の例では, i の値は12になります。

     int n = 100;
     int m = 3;
     int i;

     i = n >> m;

6.5.4 関係演算子

関係演算子は2つのオペランドを比較して,int型の結果を生成します。 関係が偽である場合には結果は0になり,真である場合には1 になります。関係演算子には,左不等号演算子( < ),右不等号演算子( > ),等価左不等号演算子( <= ),等価右不等号演算( >= )があります。両方のオペランドとも算術型であるか,または互換型へのポインタでなければなりません。 コンパイラは,比較を行う前に必要な算術変換を行います( 第6.11.1項を参照してください) 。

2つのポインタを比較した結果は,各ポインタが示す2つのオブジェクトの相対位置によって異なります。 下位アドレスのオブジェクトへのポインタは, 上位アドレスのオブジェクトへのポインタよりも小さくなります。2 つのアドレスが同じ配列の要素を示す場合には,下位の添字を持つ要素のアドレスが上位の添字を持つ要素のアドレスよりも小さくなります。

関係演算子は左から右に結合されます。したがって,次に示す文は a b に結合します。a b より小さい場合, その結果は1 (真)になります。a b よりも大きいかまたは等しい場合, その結果は0 (偽)になります。その後,その0または1が c と比較されて,式の結果が出ます。この文では, 「b a c の間であるかどうか」は決定されません。

     if ( a < b < c )
         statement;

b a c の間にあるかどうかを検査するには, 次のようにコーディングします。

     if ( a < b && b < c )
         statement;

6.5.5 等価演算子

等価演算子には等価( == )と非等価( != )があり,int型の結果を生成します。したがって, 次の文の結果は両方のオペランドが同じ値を持つ場合は1,持たない場合は0 になります。

     a == b

2つのオペランドは,次に示す型の組み合わせのいずれかでなければなりません。

オペランドは必要に応じて,通常の算術変換規則により変換されます(第6.11.1項を参照してください)。

2つのポインタまたはアドレスが同じ記憶位置を識別する場合,その2つは等しいものです。


注意
代入( = )と等価( == )にはそれぞれ異なる記号が使用されますが,C 言語では両方の演算子ともすべての文脈で使用することができます。 したがって,この2つの演算子を混同しないように注意してください。 次にその例を示します。
     if ( x = 1 ) 1;
     else 2;
この例では,文1が必ず実行されます。これは,代入 x=1 の結果が x の値と同等であり,x には常に1 (つまり真) が代入されるからです。

6.5.6 ビット演算子

ビット演算子には汎整数オペランドが必要です。通常の算術変換が行われます( 第6.11.1項を参照してください)。 式の結果は,2 つのオペランドに関するビットAND ( & ),包含OR ( | ),または排他的OR ( ^ )になります。オペランドの評価の順序は保証されません。

オペランドはビット単位で評価されます。1つのビット値が0でもう一方が1 である場合,または両方のビット値が0である場合,& 演算子の結果は0 になります。両方のビット値が1である場合,その結果は1になります。

両方のビット値が0である場合,| 演算子の結果は0になります。 いずれかのビット値が1である場合,または両方のビット値が1である場合, その結果は1になります。

両方のビット値が0である場合,または両方のビット値が1である場合,^ 演算子の結果は0になります。いずれかのビット値が1で,もう一方が0 である場合,その結果は1になります。

6.5.7 論理演算子

論理演算子には,AND (&&)とOR (||)があります。この演算子は左から右に評価されます。 式の結果( int ) は,0 (偽)または1 (真)のいずれかです。2つのオペランドは同じ型を持つ必要はありませんが, 両方の型ともスカラ型でなければなりません。コンパイラの評価が左オペランドのみを検査することにより行われる場合には, 右オペランドは評価されません。次の式を例にとります。


     E1 && E2

両方のオペランドが0以外の場合,この式の結果は1になります。またはオペランドの一方が0 である場合,式の結果は0になります。式 E1 が0である場合,式 E2 は評価されません。これは,E2 の値とは無関係にその結果が同じであるからです。

同様にいずれかのオペランドが0以外の場合,次の式は1になり,それ以外の場合は0 になります。式 E1 が0 以外の場合には,E2 は評価されません。これは, E2 の値とは無関係に結果が同じになるからです。


     E1 || E2

6.6 条件演算子

条件演算子( ?: )は,3つのオペランドをとります。 この演算子は最初のオペランドの結果を検査し,次に最初の結果に基づいて残りの2 つのオペランドのいずれか1つを評価します。次にその例を示します。


     E1  ?  E2  :  E3

E1 が0以外の(真) 場合は E2 が評価され, それが条件式の値になります。E1が0 の(偽)場合は E3 が評価され,それが条件式の値になります。 条件式は右から左に結合されます。次の例では, 条件演算子は x y の最小値を得るために使用されます。

     a = (x < y) ? x : y;     /* a = min(x, y)  */

最初の式(E1)の後に評価順序点があります。次に示す例では結果が予測できるため, 予定外の副作用が起こる可能性はありません。

     i++ > j ? y[i] : x[i];

条件演算子は左辺値を生成しません。したがって,a ? x : y = 10 のような文は有効ではありません。

条件演算子には,次の制限があります。

6.7 代入演算子

代入演算子にはいくつか種類があります。代入を行った結果の値は,その変数に入ります。 代入は,より大きい式の部分式として使用できます。代入演算子は左辺値を生成しません。

代入式は2つのオペランド,すなわち左に可変左辺値および右に式を持ちます。 単純代入は,次に示すように2つのオペランドの間に等号( = )を入れます。


     E1 = E2;

E2 の値は E1 に代入されます。型は E1 の型になり,演算終了後の結果は E1 の値になります。

複合代入は,2つのオペランドの間に等号( = )の前か後に別の2項演算子を組み合わせたものを入れます。次にその例を示します。


     E1 += E2;

この式は次に示す単純代入と同等です。ただし,複合代入では E1 は1回評価されるのに対して,単純代入では E1 が2回評価されます。


     E1 = E1 + E2;

次に示す2つの式では,代入は等しくなります。

     a *= b + 1;

     a = a * (b + 1);

この例では,次に示す式は number[1] の定数に100を加算します。

     number[1] += 100;

この式の結果は加算後の値になり,number[1] と同じ型を持ちます。

両方の代入オペランドが算術型である場合,右オペランドは代入前に左の型に変換されます( 第6.11.1項を参照してください) 。

代入演算子( = )は,構造体と共用体に値を代入するために使用できます。 Compaq CのVAX C互換性モードでは,2 つの構造体がバイト単位で同じサイズに定義されている限り, 一方の構造体をもう一方の構造体に代入できます。ANSI規格モードでは,2 つの構造体は同じ型を持たなければなりません。すべての複合代入演算子では, すべての右オペランドと左オペランドは,ポインタであるか, または算術値に評価されなければなりません。演算子が -= または += である場合には,左オペランドをポインタにすることができ,( 汎整数である)右オペランドは, 2項プラス演算子( + )と2項マイナス演算子( - )の右オペランドと同じ方法で変換されます。

複合代入演算子を構成する文字は,前後を逆に記述してはなりません。次にその例を示します。


     E1 =+ E2;

この例は古い形式で現在はサポートされていませんが,エラーが検出されずにコンパイラを通過します。 この場合,代入演算子の後に単項プラス演算子が続くものと解釈されます。

6.8 コンマ演算子

コンマ演算子で区切った複数の式は,左から右に評価されます。結果は, (他の式の副作用が起こることがあっても)一番右側の式の型と値になります。 結果は左辺値ではありません。次の例では 1 R に代入され, 2 T に代入されます。

     R = T = 1,   T += 2,   T -= 1;

それぞれの式の副作用は,次の式が評価される前に完了します

コンマが別の意味で実引数や初期化並びに現れる場合には,コンマ式は括弧で囲まなければなりません。 次にその例を示します。

     f(a, (t=3,t+2), c)

この例では,a5,および c実引数を持つ f関数を呼び出します。 さらに,t変数に 3 を代入します。

6.9 定数式

定数式とは定数のみを含む式のことです。定数式は実行時ではなくコンパイル時に評価でき, 定数を記述できる任意の場所で使用できます。次の例では limit+1 が定数式であり,コンパイル時に評価されます。

     #define limit 500
     char x[limit+1]

定数式には,代入演算子,インクリメント演算子,デクリメント演算子, 関数呼出し演算子,またはコンマ演算子を含めることはできません。 ただし,それらが sizeof演算子のオペランド内にある場合は例外です。 各定数式は,その型で表現可能な値の範囲内にある定数を評価しなければなりません。

次に,Compaq Cにおいて定数を評価する式が必要な文脈を示します。

6.9.1 汎整数定数式

汎整数定数式は汎整数型を持ち,オペランドは整数定数,列挙定数, 文字定数, オペランドに可変長配列型や括弧で囲んだ可変長配列型名を持たない sizeof式,またはキャストの直接のオペランドである浮動小数点定数でなければなりません。 汎整数定数式のキャスト演算子は, sizeof演算子のオペランドの一部である場合以外は, 算術型のみを汎整数型に変換します。

Compaq Cでは,初期化子の定数式の許容範囲を拡大することができます。 この定数式では,次のいずれかを評価できます。

6.9.2 算術定数式

算術定数式は算術型の式であり,整数定数,浮動小数点定数,列挙定数,文字定数, または, オペランドに可変長配列型や括弧で囲んだ可変長配列型名を持たない sizeof式のオペランドのみを含みます。 算術定数式のキャスト演算子は sizeof 演算子のオペランドの一部である場合以外は,算術型のみを算術型に変換します。

6.9.3 アドレス定数

アドレス定数は,静的記憶域存続期間(第2.10節を参照のこと)のオブジェクトを指定する左辺値へのポインタ, または関数指名子へのポインタです。アドレス定数は単項&演算子を使用して明示的に作成するか, または配列型か関数型の式を使用して暗黙に作成しなければなりません。 配列添字 [] およびメンバ・アクセス演算子の . -> ,アドレス演算子 & と間接指定単項演算子 * ,およびポインタ・ キャストを使用してアドレス定数を作成できます。しかし, これらの演算子を使用してオブジェクトの値にアクセスすることはできません。

6.10 複合リテラル式

複合リテラル構成子式(constructor expression)とも呼ばれ, 配列,構造体,共用体型などのオブジェクトの値を構成する式です。

C89標準では,structの値を関数に渡す場合,通常は, その型の名前付きオブジェクトを宣言し,そのメンバを初期化し, そのオブジェクトを関数に渡します。 C99標準では,この処理を1つの複合リテラル式で実行できるようになりました。 (複合リテラル式は,Compaq CコンパイラのコモンC,VAX C, Strict ANSI89モードではサポートされていないので注意してください。)

複合リテラルは名前なしオブジェクトであり, 括弧で囲んだ型名(キャスト演算子 [1] と同じ構文) の後に中括弧で囲んだ初期化子の並びを続けるという構文で指定されます。 この名前なしオブジェクトの値は,初期化子リストで決定されます。 初期化子の並びには,指名子の構文を使用できます。

たとえば,配列要素9の値が5で,それ以外の値はすべてゼロである 1000個のintの配列を作成するには,次のように記述します。

(int [1000]){[9] = 5}. 

複合リテラルのオブジェクトは左辺値です。 複合リテラルが指名するオブジェクトは,すべての関数定義の外にある場合, 静的記憶域存続期間を持ちます。 すべての関数定義の外でない場合は,囲んでいる最も近いブロックに対応する 自動記憶域存続期間を持ちます。


使用上の注意

次の例では,複合リテラルの使用方法を示します。

  1. int *p = (int []){2, 4};
    

    この例では,配列の最初の要素を指すように pを初期化します。 この配列にはintが2つあり,1番目の値は2で,2番目の値が4です。 この複合リテラルの中の式は,定数でなければなりません。 この名前なしオブジェクトは,静的記憶域存続期間を持ちます。

  2. void f(void)
    {
            int *p;
            /*...*/
            p = (int [2]){*p};
            /*...*/
    }
    

    この例では,pには配列の最初の要素のアドレスが代入されます。 この配列にはintが2つあり, 1番目の値はpが直前に指していた値で,2番目の値はゼロです。 この複合リテラルの式は,定数でなくても構いません。 この名前なしオブジェクトは,自動記憶域存続期間を持ちます。

  3. drawline((struct point){.x=1, .y=1},
            (struct point){.x=3, .y=4});
    
    または,drawlineがpoint構造体へのポインタを必要とする場合は,次のようになります。
     
    drawline(&(struct point){.x=1, .y=1},
            &(struct point){.x=3, .y=4});
    

    ディジグネーションを持つ初期化子は, 複合リテラルと組み合わせることができます。 複合リテラルを使用して作成した構造体オブジェクトは, メンバの順序とは無関係に関数に渡すことができます。

  4. (const float []){1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6}
    

    読取り専用の複合リテラルは,この例のような構文で指定できます。

  5. "/tmp/testfile"
    (char []){"/tmp/testfile"}
    (const char []){"/tmp/testfile"}
    

    この例の3つの式は,意味が異なっています。

    最初の式は,記憶域存続期間が常に静的であり,型は「charの配列」ですが, 変更可能であるとは限りません。

    後の2つは,関数本体の内部にある場合は記憶域存続期間が自動であり, 前者は変更が可能です。

  6. (const char []){"abc"} == "abc"
    

    文字列リテラルと同じように,constで修飾した複合リテラルは, 読取り専用メモリに配置でき,共用も可能です。 このリテラルの記憶域が共用されている場合,この例の結果は1になります。

  7. struct int_list { int car; struct int_list *cdr; };
    struct int_list endless_zeros = {0, &endless_zeros};
    eval(endless_zeros);
    

    複合リテラルには名前がないため,単一の複合リテラルでは, 循環してリンクされるオブジェクトを指定することはできません。 この例で言えば,名前付きオブジェクトendless_zerosの 代わりに関数の引数として使用できる,自己参照型の複合リテラルを記述する方法はありません。

  8. struct s { int i; };
    
    int f (void)
    {
            struct s *p = 0, *q;
            int j = 0;
    
            while (j < 2) 
                     q = p, p = &((struct s){ j++ });
            return p == q && q->i == 1;
    }
    

    この例に示すように,複合リテラルはそれぞれ,指定されたスコープ中で *オブジェクトを1つだけ作成します。

    f( )関数は必ず値1を返します。


[1] ただし,キャストがスカラ型またはvoidのみへの変換を指定する キャスト式とは異なり,キャスト式の結果は左辺値ではありません。

6.11 データ型変換

C言語では,次に示す4つの場合にデータ型変換を行います。

  1. 異なる型の複数のオペランドが1つの式に現れる場合

  2. char型,short型,および float型の実引数が旧形式の宣言を使用して関数に渡される場合

  3. 関数プロトタイプで宣言される仮引数に厳密に一致していない実引数が関数に渡される場合

  4. オペランドのデータ型が,キャスト演算子によって強制的に変換される場合( キャスト演算子については,第6.4.6 項を参照のこと)

これ以降の項では,オペランドと関数の実引数の変換方法について説明します。

6.11.1 通常の算術変換

次に示す規則は通常の算術変換と呼ばれ,算術式のオペランドのすべての変換に適用されます。 この算術変換ではオペランドを共通の型に変換し, この型が結果の型になります。規則は次の順序で適用されます。

  1. いずれかのオペランドが算術型のオペランド以外の場合,変換は行われない。

  2. いずれかのオペランドが long double型を持つ場合,もう一方のオペランドは long doubleに変換される。

  3. 前記以外でいずれかのオペランドが double型を持つ場合,もう一方のオペランドは double に変換される。

  4. 前記以外でいずれかのオペランドが float型を持つ場合,もう一方のオペランドは float に変換される。

  5. 複素数の場合((Alpha 専用)),上記の変換は, 変換対象のオペランドに対応する実数型で行われる。

    たとえば,double _Complexfloatを 足すと,floatのオペランドはdoubleに 変換されるだけで,結果はdouble _Complexとなる。

  6. 前記以外の場合には汎整数拡張が両方のオペランドで実行され, 次の規則が適用される。

    1. 両方のオペランドの型が同じ場合,変換はそれ以上必要ない。

    2. 型が同じでない場合,両方のオペランドが符号付き整数型であるか, 両方のオペランドが符号なし整数型であれば,整数変換ランクの低い オペランドが,ランクの高いオペランドの型に変換される。

    3. 前記以外の場合でいずれかのオペランドの型が unsigned long long intのとき, もう一方のオペランドは unsigned long long int に変換される。

    4. いずれかのオペランドの型が unsigned long int の場合,もう一方のオペランドは unsigned long int に変換される。

    5. 前記以外の場合で1つのオペランドが long int型を持ち,もう一方のオペランドが unsigned int型を持つとき,long int unsigned int の値すべてを表すことができる場合,unsigned int型のオペランドは long int に変換される。long int unsigned int の値をすべて表すことができない場合, 両方のオペランドは unsigned long int に変換される。

    6. 前記以外でいずれかのオペランドが long int型を持つ場合,もう一方のオペランドは long int に変換される。

    7. 前記以外でいずれかのオペランドが unsigned int型を持つ場合,もう一方のオペランドは unsigned int に変換される。

    8. 前記以外の場合,両方のオペランドが int型を持つ。

これ以降の項では,通常の算術変換規則について説明します。

6.11.1.1 文字と整数

符号付きまたは符号なしの charビット・ フィールド,short intビット・フィールド, または intビット・フィールド, あるいは列挙型を持つオブジェクトは,int または unsigned int が使用可能な式に使用できます。 int が元の型の値をすべて表すことができる場合には, その値は int に変換されます。それ以外の場合は unsigned int に変換されます。このような変換規則を 汎整数拡張と呼びます。

この汎整数拡張の実現を値保存と呼びます。これに対して, unsigned char unsigned short型を unsigned int に拡張することを符号なし保存と呼びます。Compaq C は,コモンCモードが指定されない限りANSI C 規格で規定されている値保存の拡張を使用します。

エラー検査オプションを指定したCompaq Cでは,符号なし保存規則に依存する算術変換の検索を容易にするために unsigned char unsigned short から int への汎整数拡張にフラグを立てます。 これは,値保存の汎整数拡張により影響を受ける可能性があります。

その他のすべての算術型は,汎整数拡張により変更されることはありません。

Compaq Cでは,char型の変数は符号付き整数として処理されるバイトです。 長い整数が短い整数または char に変換されると切り捨てが左端で行われ, 超過したビットは破棄されます。次にその例を示します。

     int i;
     char c;

     i = 0xFFFFFF41;
     c = i;

このコーディングでは,16進の41 ('A' )を c に代入します。コンパイラは,符号拡張によって短い符号付き整数を長い整数に変換します。

6.11.1.2 符号付き整数と符号なし整数

変換はさまざまな種類の整数の間でも行われます。

汎整数型を持つ値を別の汎整数型に変換した場合(例: int long int に変換した場合) ,値を新しい型で表すことができる場合にはその値は変更されません。

符号付き整数をそれ以上のサイズを持つ符号なし整数に変換したとき,その符号付き整数値が負以外の場合には値は変更されません。 符号付き整数値が負である場合には次の変換が行われます。

整数値を小さいサイズの符号なし整数に縮小する場合には,その結果はその新しい汎整数型で表せる最大の符号なしの値よりも大きい値で割った値の負以外の剰余になります。

整数値を小さいサイズの符号付き整数に縮小する場合,または符号なし整数をそれに対応する符号付き整数に変換する場合には, その値は新しい型で表すのに十分なサイズであれば変更されません。 それ以外の場合には, 結果は切り捨てられます。つまり,超過した上位ビットが破棄され,精度は損なわれます。

同じサイズの汎整数型間での変換の場合には,符号付きでも符号なしでもマシン・ レベルの表現は変更されません。

6.11.1.3 浮動小数点と汎整数

浮動小数点型オペランドが整数に変換されると,小数部は切り捨てられます。

コンパイル時に浮動小数点型の値を整数型または別の浮動小数点型に変換したとき, その結果を表すことができない場合には,コンパイラは次に示す場合に対して警告を出します。

汎整数型の値を浮動小数点型に変換したとき,その値が表現できる値の範囲内にはあるが厳密ではない場合には, 変換の結果は次に大きい値または小さい値になります。 いずれの値になるかは,各ハードウェアの変換結果により異なります。 使用する各プラットフォームの変換結果については, プラットフォームに固有のCompaq Cのマニュアルを参照してください。

6.11.1.4 浮動小数点型

式の中に float型のオペランドが含まれる場合には, そのオペランドは単精度オブジェクトとみなされます。ただし, その式に double型または long double型のオブジェクトも含まれる場合には,通常の算術変換が適用されます。

float double long double に拡張される場合, または double long double に拡張される場合には,その値は変更されません。

double float に縮小する場合,または long double double float に縮小する場合,変換する値が表現できる値の範囲外にあるとその動作結果は定義されていません。

変換する値が表現できる値の範囲内にあるが厳密ではない場合には, その結果は次に大きいか,または小さい表現可能な float値に丸められます。

6.11.2 ポインタ変換

2つの型(例: int long )は同じ表現を持つことはできますが,それぞれ異なる型です。 つまり,int へのポインタはキャストを使用せずに long へのポインタに代入することはできません。 また,ある型の関数へのポインタはキャストを使用せずに異なる型の関数へのポインタに代入することもできません。 さらに,異なる仮引数型情報(仮引数型情報を持たない旧形式を含む) を持つ関数へのポインタの型は,異なる型です。このような場合にキャストを使用しなければ, コンパイラはエラーを出します。プロセッサの中には境界調整の制限を持つものがあるため, 境界調整されていないポインタでアクセスすると, アクセス時間がかかったり,マシン例外が起こることがあります。

void へのポインタと,任意の不完全型またはオブジェクト型へのポインタは相互に変換できます。 不完全型またはオブジェクト型へのポインタが void へのポインタに変換され, 元に戻されると,その結果は元のポインタに等しくなります。

void * 型へキャストされた0に等しい汎整数定数式または式を, 空ポインタ定数と呼びます。空ポインタ定数があるポインタに代入されるか, またはポインタに等しいかどうか比較される場合, その定数はその型のポインタに変換されます。このようなポインタを 空ポインタと呼び,任意のオブジェクトまたは関数へのポインタと比較しても等しくなりません。

配列指名子は配列型へのポインタに自動的に変換され,そのポインタは配列の最初の要素を示します。

6.11.3 関数の実引数の変換

関数プロトタイプ宣言が存在しなければ,関数の実引数のデータ型は仮引数の型に一致するものとみなされます。 関数プロトタイプが存在すると, 関数呼出しの実引数がすべて関数プロトタイプ宣言に宣言されているすべての仮引数との代入互換性を持つかどうか比較されます。 実引数の型が仮引数の型とは一致しないけれども代入互換性を持つ場合,C 言語はその実引数をその仮引数の型に変換します( 第6.11.1 項を参照してください)。関数呼出しの実引数が関数プロトタイプ宣言に宣言されている仮引数と代入互換性を持たない場合には, エラー・メッセージが出されます。

関数プロトタイプが存在しなければ, float型のすべての実引数が double に,char または short型のすべての実引数が int型に,unsigned char unsigned short型のすべての実引数が unsigned int に変換されます。配列名または関数名は, 指名された配列または関数のアドレスにそれぞれ変換されます。 コンパイラはその他の変換を自動的には行いません。このような変換の後に不互換が生じた場合はプログラム・ エラーです。

関数指名子は関数型を持つ式です。この指名子が sizeof演算子のオペランドまたは単項&演算子である場合以外は, 「を返す関数」型を持つ関数指名子は「を返す関数へのポインタ」型を持つ式に変換されます。