GCC,-O2和位域 - 这是一个错误还是一个功能?

时间:2010-05-14 19:23:16

标签: c++ c gcc bit-fields optimization

今天,我在尝试使用位字段时发现了令人担忧的行为。为了便于讨论和简化,这是一个示例程序:

#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

我承认属性是一个可疑的东西;在这种情况下,我关注的是优化设置的差异。

1 个答案:

答案 0 :(得分:8)

如果您想获得技术,那么您使用__attribute__(包含两个连续下划线的标识符)的那一刻您的代码就有/未定义的行为。

如果你对删除的那些行为有同样的行为,那么它在我看来就像编译错误一样。将3位字段视为7这一事实意味着它被视为无符号,因此当您溢出时,它应该像任何其他无符号一样,并为您提供模运算。

将比特字段视为已签名也是合法的。在这种情况下,第一个结果是-1-3-0(可能只打印为0),第二个结果是未定义的(因为有符号整数的溢出给出了未定义的行为)。理论上,在C89或当前的C ++标准下,其他值可能是可能的,因为它们不限制有符号整数的表示。在C99或C ++ 0x中,它只能是那三个(C99限制有符号整数到一个补码,二进制补码或符号幅度,C ++ 0x基于C99而不是C90)。

哎呀:我没有给予足够的关注 - 因为它被定义为unsigned,它必须被视为unsigned,留下一点摆动它成为编译器错误的余地