今天,我在尝试使用位字段时发现了令人担忧的行为。为了便于讨论和简化,这是一个示例程序:
#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: %d\n",(int)n.d);
n.d++;
printf("3-bit field cast to int: %d\n",(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对此有所了解?
编辑(re:signed / unsigned):
它被视为无符号,因为它被声明为无符号。将它声明为int得到输出(使用O0):
将3位字段强制转换为int:-1
3位字段转换为int:0
在这种情况下,-O2会发生更有趣的事情:
3位字段转换为int:7
3位字段转换为int:8
我承认属性是一个可疑的东西;在这种情况下,我关注的是优化设置的差异。
答案 0 :(得分:8)
如果您想获得技术,那么您使用__attribute__
(包含两个连续下划线的标识符)的那一刻您的代码就有/未定义的行为。
如果你对删除的那些行为有同样的行为,那么它在我看来就像编译错误一样。将3位字段视为7
这一事实意味着它被视为无符号,因此当您溢出时,它应该像任何其他无符号一样,并为您提供模运算。
将比特字段视为已签名也是合法的。在这种情况下,第一个结果是-1
,-3
或-0
(可能只打印为0
),第二个结果是未定义的(因为有符号整数的溢出给出了未定义的行为)。理论上,在C89或当前的C ++标准下,其他值可能是可能的,因为它们不限制有符号整数的表示。在C99或C ++ 0x中,它只能是那三个(C99限制有符号整数到一个补码,二进制补码或符号幅度,C ++ 0x基于C99而不是C90)。
哎呀:我没有给予足够的关注 - 因为它被定义为unsigned
,它必须被视为unsigned
,留下一点摆动它成为编译器错误的余地