我正在这里了解标志扩展: 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个问题:
编辑: 4.为什么不在32位系统中出现问题?
答案 0 :(得分:3)
<<
运算符的左操作数经历了标准促销,因此在您的情况下,它被提升为int
- 到目前为止一直很好。接下来,值int
的{{1}}乘以2 13 ,这会导致溢出,从而导致未定义的行为。但是,我们可以看到发生了什么:表达式的值现在只是0x4000
,最小的可表示INT_MIN
。最后,当您将其转换为无符号的64位整数时,通常的模运算规则要求结果值为int
。同样,转换为无符号的32位整数会得到值0xffffffff80000000
。
要对无符号值执行操作,您需要使用强制转换来控制转换:
0x80000000
答案 1 :(得分:1)
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)
是无符号(当然是肯定的)值。
编辑:他的回答a.base << 13
中提到的KerrekSB实际上无法代表int
(我假设32位int
)所以这个表达式调用未定义的行为,并且实现有权以任何其他方式行事,例如崩溃。
有关信息,这绝对不是可移植的,但如果您使用的是gcc
,则gcc
在此处不会将a.base << 13
视为未定义的行为。来自gcc
文档:
http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html 中的“GCC不仅仅使用C99中给出的宽容度来处理签名'&lt;&lt;&lt;'的某些方面如未定义,但这可能会发生变化。“
答案 2 :(得分:0)
这是关于位域的更多问题。请注意,如果将结构更改为
struct foo {
unsigned int base, rehash;
};
你会得到非常不同的结果。
正如@ {JensGustedt在{{3}}中所说,规范说:
如果int可以表示原始类型的所有值(受宽度限制,对于位域),则该值将转换为int;
即使您已指定base是无符号的,编译器在您读取时也会将其转换为signed int
。这就是为什么当你将它转换为unsigned int
时你没有得到符号扩展。
符号扩展与负数如何以二进制表示有关。最常见的方案是2s补码。在这个方案中,-1以32位表示为0xFFFFFFFF,-2是0xFFFFFFFE等。那么,当我们想要将32位数转换为64位数时应该怎么做?如果我们将0xFFFFFFFF转换为0x00000000FFFFFFFF,则数字将具有相同的无符号值(约40亿),但签名值不同(-1对40亿)。另一方面,如果我们将0xFFFFFFFF转换为0xFFFFFFFFFFFFFFFF,则数字将具有相同的有符号值(-1)但不同的无符号值。前者称为零扩展(适用于无符号数),后者称为符号扩展(适用于带符号数)。它被称为“符号扩展”,因为“符号位”(最重要或最左边的位)被扩展或复制,以使数字更宽。
答案 3 :(得分:0)
我花了一段时间和很多阅读/测试
也许我,初学者的方式来了解发生了什么将会得到你(因为我得到它)
unsigned long
(64位),因此要将赋值righ值转换为long int。从signed int到long int的转换使addr(1)x33,(0)x31。这就是在您甚至不知道的所有转换之后打印的内容:
0xffffffff80000000
。
为什么第二行打印0x80000000
是因为在转换为long int
之前转换为(unsigned int)。将unsigned int
转换为long int
时,没有位符号,因此值只会填充尾随0以匹配大小,而这就是全部。
与32位有什么不同,在从32-bit signed int
到32-bit unsigned long
的转换过程中,它们的大小匹配,并且会添加尾随位符号,因此:
1(0)x31
将保留1(0)x31
甚至在从int转换为long int之后(它们具有相同的大小,该值被解释为不同但位完整。)
来自您链接的报价:
任何进行此假设的代码都必须更改为适用于这两者 ILP32和LP64。虽然int和long在ILP32中都是32位 数据模型,在LP64数据模型中,long是64位。