型の変換と拡張規則はいくぶん曖昧ですから、64 ビットコンパイル環境への移行で、符号拡張はよく問題になります。符号拡張の問題を避けるには、明示的な型変換を使用して、意図した結果を得られるようにしてください。
符号拡張が発生する理由を理解するには、ISO C の変換規則の知識が役立ちます。32 ビットと 64 ビットコンパイル環境間で最大の符号拡張問題を引き起こすと思われる変換規則は、次の処理で適用されます。
整数の拡張
整数を必要とする式では、符号の有無に関係なく、char、short、enumerated type、ビットフィールドを使用することができます。
整数が元の型が取り得る値をすべて保持できる場合、値は整数に変換され、それ以外の場合は、符号なし整数に変換されます。
符号付きと符号なし整数間の変換
負符号付きの整数を同じまたは大きい型の符号なし整数に拡張する場合は、最初に大きな型符号付き整数に拡張され、次に符号なし値に変換されます。
次のコードを 64 ビットプログラムとしてコンパイルすると、addr と a.base の両方が符号なしの型であっても、addr 変数は符号拡張されます。
%cat test.c struct foo { unsigned int base:19, rehash:13; }; main(int argc, char *argv[]) { struct foo a; unsigned long addr; a.base = 0x40000; addr = a.base << 13; /* Sign extension here! */ printf("addr 0x%lx\n", addr); addr = (unsigned int)(a.base << 13); /* No sign extension here! */ printf("addr 0x%lx\n", addr); } |
ここで符号拡張が起きるのは、次のように変換規則が適用されるためです。
a.base は、整数拡張規則により符号なし int から int に変換されます。つまり、式の a.base << 13 は int 型ですが、符号拡張はまだ発生していません。
式の a.base << 13 は int 型ですが、符号付きと符号なし整数拡張規則により、addr に代入する前に long、次に符号なし long へと変換されます。符号拡張は、int から long に変換したときに発生します。
% cc -o test64 -xarch=v9 test.c % ./test64 addr 0xffffffff80000000 addr 0x80000000 % |
同じ例を 32 ビットプログラムとしてコンパイルすると、符号拡張はまったく表示されません。
cc -o test test.c %test addr 0x80000000 addr 0x80000000 |
変換規則の詳細については、ANSI/ISO C 規格の仕様書を参照してください。この規格には通常の演算変換や整数定数に関する有用な規則も規定されています。