符号の拡張は、64 ビットに変換する際によく発生する問題です。符号の拡張について lint(1) は警告を出さないため、実際に問題が発生する前に問題を検出するのは困難です。さらに、型変換および型昇格に関する規則には不明瞭な部分もあります。符号拡張の問題を解決するには、意図する結果を得ることができるように明示的なキャストを使用する必要があります。
ANSI C の変換規則を理解しておくと、なぜ符号の拡張が発生するかを理解するのに役立ちます。32 ビットおよび 64 ビットの整数値間において、符号拡張の問題の原因になることがある変換規則は、次のとおりです。
整数の昇格
char
、short
、列挙型、または ビットフィールド型は、符号付きあるいは符号なしに関わらず、int
を呼び出す式の中で使用できます。int
が元の型の取り得る値をすべて格納することができる場合は、その値は signed int
に変換されます。そうでない場合は、unsigned int
に変換されます。
符号付きおよび符号なし整数間の変換
負の符号付き整数が、サイズが同じまたはより大きい型の符号なし整数に昇格される場合、最初に大きい型の符号付きの値に昇格され、その後符号なしの値に変換されます。
変換規則についての詳細は、ANSI C 規格を参照してください。この規格には、通常の算術変換や整数定数についての規則が規定されています。
64 ビットプログラムとしてコンパイルした場合、次の例の addr
変数は、addr
および a.base
が符号なしの型であっても符号付きの型に拡張されます。
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 が、整数の昇格規則によって、unsigned int
から int
に変換されます。このため、式 a.base << 13
は int
型ですが、符号拡張はまだ発生していません。
式 a.base << 13 は、int
型ですが、符号付きおよび符号なしの整数昇格規則によって最初に long
へ変換され、その後 unsigned long
へ変換された後、addr
に代入されます。符号拡張は、この式が int
から long
に変換されるときに発生します。
% cc -o test64 -xarch=v9 test.c % ./test64 addr 0xffffffff80000000 addr 0x80000000 % |
同じ例題が 32 ビットプログラムとしてコンパイルされた場合、符号拡張は発生しません。
% cc -o test32 test.c % ./test32 addr 0x80000000 addr 0x80000000 % |