今天我在试验位域时发现了令人震惊的行为。为了讨论和简单起见,这里有一个示例程序:
#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-bit field cast to int: 7
3-bit field cast to int: 0
这是使用“g++ -O2”(和 -O3)编译时的输出:
3-bit field cast to int: 7
3-bit field cast to 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-bit field cast to int: -1
3-bit field cast to int: 0
在这种情况下,-O2 会发生更有趣的事情:
3-bit field cast to int: 7
3-bit field cast to int: 8
我承认 attribute 使用起来有点可疑;在这种情况下,我关心的是优化设置的差异。
最佳答案
如果您想获得技术知识,在您使用 __attribute__
(包含两个连续下划线的标识符)的那一刻,您的代码就有/有未定义的行为。
如果你在删除那些后得到相同的行为,在我看来它就像一个编译器错误。一个 3 位字段被视为 7
的事实意味着它被视为一个无符号的,所以当你溢出时它应该像任何其他无符号的一样,并给你模算术。
将位字段视为已签名也是合法的。在这种情况下,第一个结果将是 -1
、-3
或 -0
(可能打印为 0
),第二个未定义(因为有符号整数的溢出会导致未定义的行为)。理论上,其他值在 C89 或当前的 C++ 标准下可能是可行的,因为它们不限制有符号整数的表示。在 C99 或 C++0x 中,它只能是这三个(C99 将有符号整数限制为 1 的补码、2 的补码或符号大小,C++0x 是基于 C99 而不是 C90)。
糟糕:我没有给予足够的关注——因为它被定义为 unsigned
,所以它必须被视为 unsigned
,留下很小的回旋余地它是一个编译器错误。
关于c++ - GCC、-O2 和位域——这是错误还是功能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2836978/