转换到 64 位时,经常会遇到非故意符号扩展问题。该问题很难在实际发生前检测到,因为 lint(1) 不会针对它发出警告。此外,类型转换和提升规则也有些模糊。要解决非故意符号扩展的问题,必须使用显式强制类型转换来实现预期结果。
要了解出现符号扩展的原因,了解 ANSI C 的转换规则会有所帮助。以下是可能会导致 32 位和 64 位整数值之间大多数符号扩展问题的转换规则:
整型提升
无论有无符号,均可以在任何调用类型为 int
的表达式中使用 char
、short
、枚举类型或位字段。如果 int
可以支持初始类型的所有可能值,则值会转换为 int
类型。否则,值会转换为 unsigned int
类型。
带符号整数和无符号整数之间的转换
将带负号的整数提升为同一类型或更长类型的无符号整数时,该整数首先提升为更长类型的带符号相同值,然后再转换为无符号值。
有关转换规则的更详细讨论,请参阅 ANSI C 标准。该标准中还包括适用于普通运算转换和整数常量的规则。
以下示例编译为 64 位程序时,即使 addr
和 a.base
均是 unsigned
类型,addr
变量仍可成为带符号扩展变量。
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
,但是在赋值给 addr 之前,由于带符号和无符号整型提升规则,会转换为类型 long
,然后再转换为类型 unsigned long
。从类型 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 % |