C中的符号扩展

时间:2013-10-08 23:02:38

标签: c bit

我正在这里了解标志扩展: 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位(指addr)?
  3. 当我输入信头时,为什么没有符号扩展?
  4. 编辑: 4.为什么不在32位系统中出现问题?

4 个答案:

答案 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文档:

  

“GCC不仅仅使用C99中给出的宽容度来处理签名'&lt;&lt;&lt;'的某些方面如未定义,但这可能会发生变化。“

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

中的

答案 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)

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

  1. a.base = 0x40000(1(0)x18) - &gt; 19位位域
  2. ADDR = a.base&LT;&。13
    • a.base可以保存int的任何值都可以保持,从19位无符号int位域转换为32位有符号整数。 (a.base现在是(0)x13,1,(0)x18)。
    • now(转换为signed int a.base)&lt;&lt; 13,结果为1(0)x31)。记住它现在已经签名了。
    • ADDR =(1(0)X31)。 addr的类型为unsigned long(64位),因此要将赋值righ值转换为long int。从signed int到long int的转换使addr(1)x33,(0)x31。
  3. 这就是在您甚至不知道的所有转换之后打印的内容: 0xffffffff80000000
    为什么第二行打印0x80000000是因为在转换为long int之前转换为(unsigned int)。将unsigned int转换为long int时,没有位符号,因此值只会填充尾随0以匹配大小,而这就是全部。

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

    来自您链接的报价:

      

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