位字段溢出

时间:2011-02-05 17:19:05

标签: c overflow bit-fields

我可以相信每次访问位字段时C编译器都会模2 ^ n吗? 或者是否存在任何编译器/优化,其中下面的代码不会打印溢出?

struct {
  uint8_t foo:2;
} G;

G.foo = 3;
G.foo++;

if(G.foo == 0) {
  printf("Overflow\n");
}

先谢谢,Florian

4 个答案:

答案 0 :(得分:12)

是的,你可以信任C编译器在这里做正确的事情,只要用无符号类型声明位字段,你就可以使用uint8_t。根据C99标准§6.2.6.1/ 3:

  

存储在无符号位域中的值和unsigned char类型的对象应使用纯二进制表示法表示。 40)

来自§6.7.2.1/ 9:

  

位字段被解释为由指定位数组成的有符号或无符号整数类型。 104)如果值0或1存储在非零宽度位域中类型_Bool,位字段的值应等于存储的值。

从§6.2.5/ 9(强调我的):

  

有符号整数类型的非负值范围是相应无符号整数类型的子范围,每种类型中相同值的表示形式相同。 31) A涉及无符号操作数的计算永远不会溢出,因为无法用结果无符号整数类型表示的结果将以模数的形式减少,该数字大于可由结果类型表示的最大值。

所以是的,你可以确定任何符合标准的编译器都会G.foo溢出到0而没有任何其他不必要的副作用。

答案 1 :(得分:3)

没有。编译器为字段分配2位,递增3导致100b,当置于两位时导致0。

答案 2 :(得分:0)

简短回答:是的,你可以信任模2 ^ n发生。

在你的节目中, G.foo++;实际上等同于G.foo = (unsigned int)G.foo + 1

无符号整数运算总是产生2 ^(无符号整数位的int)结果。然后将这两个最小权重的比特存储在G.foo中,产生零。

答案 3 :(得分:0)

是的。我们可以从 assembly 获得答案。 这是我在Ubuntu 16.04、64位,gcc中编写代码的示例。

#include <stdio.h>

typedef unsigned int uint32_t;

struct {
  uint32_t foo1:8;
  uint32_t foo2:24;
} G;

int main() {
    G.foo1 = 0x12;
    G.foo2 = 0xffffff; // G is 0xfffff12
    printf("G.foo1=0x%02x, G.foo2=0x%06x, G=0x%08x\n", G.foo1, G.foo2, *(uint32_t *)&G);
    G.foo2++; // G.foo2 overflow
    printf("G.foo1=0x%02x, G.foo2=0x%06x, G=0x%08x\n", G.foo1, G.foo2, *(uint32_t *)&G);
    G.foo1 += (0xff-0x12+1); // // G.foo1 overflow
    printf("G.foo1=0x%02x, G.foo2=0x%06x, G=0x%08x\n", G.foo1, G.foo2, *(uint32_t *)&G);
    return 0;
}

使用gcc -S <.c file>进行编译。您可以获取程序集文件.s。在这里,我显示G.foo2++;的程序集,并写一些注释。

movl    G(%rip), %eax
shrl    $8, %eax    #  0xfffff12-->0x00ffffff
addl    $1, %eax    # 0x00ffffff+1=0x01000000
andl    $16777215, %eax # 16777215=0xffffff, so eax still 0x01000000
sall    $8, %eax    # 0x01000000-->0x00000000
movl    %eax, %edx  # edx high-24bit is fool2
movl    G(%rip), %eax   # G.foo2, tmp123
movzbl  %al, %eax   # so eax=0x00000012
orl     %edx, %eax  # eax=0x00000012 | 0x00000000 = 0x00000012
movl    %eax, G(%rip)   # write to G

我们可以看到编译器将使用移位指令来确保您所说的内容。(注意:G的内存布局为:

----------------------------------
|     foo2-24bit     | foo1-8bit |
----------------------------------

当然,前面提到的结果是:

G.foo1=0x12, G.foo2=0xffffff, G=0xffffff12
G.foo1=0x12, G.foo2=0x000000, G=0x00000012
G.foo1=0x00, G.foo2=0x000000, G=0x00000000