O2和位域
今天,我在试验比特场时发现了令人震惊的行为。 为了讨论和简单起见,下面是一个示例程序:
#include <stdio.h>
struct Node
{
int a:16 __attribute__ ((packed));
int b:16 __attribute__ ((packed));
unsigned int c:27 __attribute__ ((packed));
unsigned int d:3 __attribute__ ((packed));
unsigned int e:2 __attribute__ ((packed));
};
int main (int argc, char *argv[])
{
Node n;
n.a = 12345;
n.b = -23456;
n.c = 0x7ffffff;
n.d = 0x7;
n.e = 0x3;
printf("3-bit field cast to int: %dn",(int)n.d);
n.d++;
printf("3-bit field cast to int: %dn",(int)n.d);
}
该程序故意导致3位位域溢出。 这是使用“g ++ -O0”编译时的(正确)输出:
3位字段强制转换为int:7
3位字段强制转换为int:0
这是使用“g ++ -O2”(和-O3)编译时的输出:
3位字段强制转换为int:7
3位字段强制转换为int:8
检查后一个例子的程序集,我发现这个:
movl $7, %esi
movl $.LC1, %edi
xorl %eax, %eax
call printf
movl $8, %esi
movl $.LC1, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
优化刚插入“8”,假设7 + 1 = 8,实际上数字溢出且为零。
幸运的是,我所关心的代码并没有像我所知的那样溢出,但是这种情况让我感到害怕 - 这是一个已知的错误,一个特性,还是这种预期的行为? 我什么时候可以期待gcc对此有所了解?
编辑(重新签名/未签名):
它被视为未签名,因为它被声明为无符号。 声明它为int你得到输出(与O0):
3位字段强制转换为int:-1
3位字段强制转换为int:0
在这种情况下,-O2会发生更有趣的事情:
3位字段强制转换为int:7
3位字段强制转换为int:8
我承认这个属性是一个可憎的东西, 在这种情况下,这是我关心的优化设置的差异。
如果你想获得技术,你使用__attribute__
(一个包含两个连续下划线的标识符)的那一刻,你的代码就会有/未定义的行为。
如果你对那些被删除的行为有相同的行为,它就像是一个编译器错误。 3位字段被视为7
意味着它被视为无符号的事实,所以当你溢出它应该像其他任何未签名的,并给你模运算。
将位字段视为已签名也是合法的。 在这种情况下,第一个结果将是-1
, -3
或-0
(可能只打印0
),第二个未定义(因为有符号整数的溢出会导致未定义的行为)。 理论上,在C89或当前的C ++标准下,其他值可能是可能的,因为它们不限制有符号整数的表示。 在C99或C ++ 0x中,它只能是那三个(C99限制有符号整数到补码,二进制补码或符号幅度,C ++ 0x是基于C99而不是C90)。
糟糕:我没有给予足够的关注 - 因为它被定义为unsigned
,所以它必须被视为unsigned
,为摆脱它的编译器错误留下一点回旋余地。
上一篇: O2, and bitfields