2

我在这里查看符号扩展: 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. 什么是符号扩展?是的,我阅读了 wiki,但不明白何时发生类型提升,符号扩展是怎么回事?
  2. 为什么 ffff .. 在 64 位(参考地址)?
  3. 当我进行类型转换时,为什么没有符号扩展?

编辑: 4. 为什么不是 32 位系统的问题?

4

4 回答 4

3

运算符的左操作数<<经过标准提升,因此在您的情况下,它被提升为int-- 到目前为止还不错。接下来,intof 值0x4000乘以 2 13,这会导致溢出,从而导致未定义的行为。但是,我们可以看到正在发生的事情:表达式的值现在是 simple INT_MIN,是可表示的最小的int。最后,当您将其转换为无符号 64 位整数时,通常的模算术规则要求结果值为0xffffffff80000000. 类似地,转换为无符号 32 位整数会给出值0x80000000

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

(unsigned int)(a.base) << 13
于 2013-10-08T23:18:59.733 回答
1
a.base << 13

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

所以这相当于:

    (int) a.base << 13

这是 type 的负值int

然后:

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

将此带符号的负值 ( (int) a.base << 13)转换addrunsigned 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.base << 13实际上在int(我假设是 32 位int)中无法表示,所以这个表达式会调用未定义的行为,并且实现有权以任何其他方式表现,例如崩溃。

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

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

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

于 2013-10-08T23:20:00.443 回答
0

我花了一段时间和大量的阅读/测试。
也许我的初学者理解正在发生的事情的方式会影响到你(我知道了)

  1. a.base=0x40000 (1(0)x18) -> 19 位位域
  2. 地址=a.base<<13。
    • a.base 可以保存的任何值 int 也可以保存,因此从 19 位无符号 int 位域转换为 32 位有符号整数。(a.base 现在是 (0)x13,1,(0)x18)。
    • 现在(转换为带符号的 int a.base)<<13,结果为 1(0)x31)。请记住它现在已签名 int。
    • 地址=(1(0)x31)。addr 是unsigned long类型(64 位),因此要进行赋值,将右值转换为 long int。从signed int 到long int 的转换使addr (1)x33,(0)x31。

这就是在您甚至不知道的所有转换之后打印的内容: 0xffffffff80000000.
为什么第二行打印0x80000000是因为转换为long int. 当转换unsigned intlong int没有位符号时,值只是用尾随的 0 填充以匹配大小,仅此而已。

32 位的不同之处在于,在从它们的大小转换32-bit signed int32-bit unsigned long它们的大小匹配期间并添加尾随位符号,因此: 即使在从 int 转换为 long int 之后1(0)x31也会保持(它们具有相同的大小,值被解释不同但位1(0)x31
完好无损。)

来自您的链接的报价:

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

于 2013-10-09T15:20:34.080 回答
0

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

struct foo {
    unsigned int    base, rehash;  
};

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

正如@JensGustedt 在Type of unsigned bit-fields: int or unsigned int中指出的那样,规范说:

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

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

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

于 2013-10-08T23:29:04.853 回答