C中的符号扩展

我正在这里了解标志扩展: http : //www.shrubbery.net/solaris9ab/SUNWdev/SOL64TRANS/p8.html

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); } 

他们声称:

—————— 64位:

 % cc -o test64 -xarch=v9 test.c % ./test64 addr 0xffffffff80000000 addr 0x80000000 % 

—————— 32位:

 % cc -o test32 test.c % ./test32 addr 0x80000000 addr 0x80000000 % 

我有3个问题:

  1. 什么是签名延期? 是的,我读过维基,但是不明白何时进行类型促销,标志扩展会发生什么?
  2. 为什么ffff ..在64位(指的是addr)?
  3. 当我输入类型,为什么没有符号扩展?

编辑:4。为什么不在32位系统中出现问题?

 a.base << 13 

按位运算符对其两个操作数执行整数提升。

所以这相当于:

  (int) a.base << 13 

这是int类型的负值。

然后:

 addr = (int) a.base << 13; 

将此带符号的负值( (int) a.base << 13 )转换为addr的类型,该值通过整数转换为unsigned long整数。

整数转换(C99,6.3.1.3p2)规则与执行相同:

 addr = (long) ((int) a.base << 13); 

转换long在此处执行符号扩展,因为((int) a.base << 13)是负的有符号数。

在另一种情况下,使用一个演员你有相当于:

 addr = (unsigned long) (unsigned int) ((int) a.base << 13); 

因此,在第二种情况下不执行符号扩展,因为(unsigned int) ((int) a.base << 13)是无符号(当然是正数)值。

编辑 :正如KerrekSB在他的回答中提到的,a a.base << 13 base a.base << 13实际上在int是不可表示的(我假设是32位int )所以这个表达式调用未定义的行为,并且实现他有权以任何其他方式行事,例如崩溃。

有关信息,这绝对不是可移植的,但如果您使用gccgcc不会将a.base << 13视为未定义的行为。 来自gcc文档:

“GCC不使用C99中给出的宽容度仅将已签名的'<<'的某些方面视为未定义,但这可能会发生变化。”

在http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html中

<<运算符的左操作数经历了标准的促销,所以在你的情况下它被提升为int - 到目前为止一直很好。 接下来,将值0x4000int乘以2 13 ,这会导致溢出,从而导致未定义的行为。 但是,我们可以看到发生了什么:表达式的值现在只是INT_MIN ,最小的可表示的int 。 最后,当您将其转换为无符号的64位整数时,通常的模运算规则要求结果值为0xffffffff80000000 。 类似地,转换为无符号的32位整数会得到值0x80000000

要对无符号值执行操作,您需要使用强制转换来控制转换:

 (unsigned int)(a.base) << 13 

这更像是关于位域的问题。 请注意,如果将结构更改为

 struct foo { unsigned int base, rehash; }; 

你会得到非常不同的结果。

正如@JensGustedt在无符号位字段类型中指出的那样:int或unsigned int规范说:

如果int可以表示原始类型的所有值(由宽度限制,对于位字段),该值将转换为int;

即使您已指定base是无符号的,编译器在读取它时也会将其转换为signed int 。 这就是为什么当你将它转换为unsigned int时你没有得到符号扩展。

符号扩展与负数如何以二进制表示有关。 最常见的方案是2s补码。 在这个方案中,-1以32位表示为0xFFFFFFFF,-2是0xFFFFFFFE等。那么当我们想要将32位数转换为64位数时应该怎么做? 如果我们将0xFFFFFFFF转换为0x00000000FFFFFFFF,则数字将具有相同的无符号值(约40亿),但签名值不同(-1对40亿)。 另一方面,如果我们将0xFFFFFFFF转换为0xFFFFFFFFFFFFFFFF,则数字将具有相同的有符号值(-1)但不同的无符号值。 前者称为零扩展(适用于无符号数),后者称为符号扩展(适用于有符号数)。 它被称为“符号扩展”,因为“符号位”(最重要或最左边的位)被扩展或复制,以使数字更宽。

我花了一段时间和很多阅读/测试。
也许我的,初学者的方式来了解正在发生的事情会得到你(因为我得到它)

  1. a.base = 0x40000(1(0)x18) – > 19位位域
  2. ADDR = a.base << 13。
    • a.base可以保存int的任何值都可以保持,从19位无符号int位域转换为32位有符号整数。 (a.base现在是(0)x13,1,(0)x18)。
    • now(转换为signed int a.base)<< 13,结果为1(0)x31)。 记住它现在已经签名了。
    • ADDR =(1(0)X31)。 addr是unsigned long类型(64位),因此要将赋值righ值转换为long int。 从signed int到long int的转换使addr(1)x33,(0)x31。

这就是在你甚至都不知道的所有thos转换之后打印的内容: 0xffffffff80000000
为什么第二行打印0x80000000是因为转换为long int 之前转换为(unsigned int)。 当将unsigned int转换为long int ,没有位符号,因此值只用尾随0填充以匹配大小,这就是全部。

与32位的不同之处在于,在从32-bit signed int转换为32-bit signed int 32-bit unsigned long 32-bit signed int ,它们的大小匹配并且添加了尾随位符号,因此: 1(0)x31将保持1(0)x31
甚至在从int转换为long int之后(它们具有相同的大小,该值被解释为不同但位完整。)

从您的链接报价:

任何使用此假设的代码都必须更改为适用于ILP32和LP64。 虽然int和long在ILP32数据模型中都是32位,但在LP64数据模型中,long是64位。