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个问题:
- 什么是签名延期? 是的,我读过维基,但是不明白何时进行类型促销,标志扩展会发生什么?
- 为什么ffff ..在64位(指的是addr)?
- 当我输入类型,为什么没有符号扩展?
编辑: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
)所以这个表达式调用未定义的行为,并且实现他有权以任何其他方式行事,例如崩溃。
有关信息,这绝对不是可移植的,但如果您使用gcc
, gcc
不会将a.base << 13
视为未定义的行为。 来自gcc
文档:
“GCC不使用C99中给出的宽容度仅将已签名的'<<'的某些方面视为未定义,但这可能会发生变化。”
在http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html中
<<
运算符的左操作数经历了标准的促销,所以在你的情况下它被提升为int
- 到目前为止一直很好。 接下来,将值0x4000
的int
乘以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)但不同的无符号值。 前者称为零扩展(适用于无符号数),后者称为符号扩展(适用于有符号数)。 它被称为“符号扩展”,因为“符号位”(最重要或最左边的位)被扩展或复制,以使数字更宽。
我花了一段时间和很多阅读/测试。
也许我的,初学者的方式来了解正在发生的事情会得到你(因为我得到它)
- a.base = 0x40000(1(0)x18) – > 19位位域
- 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位。