位域溢出

发布于 2024-10-15 19:01:12 字数 211 浏览 1 评论 0原文

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

struct {
  uint8_t foo:2;
} G;

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

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

提前致谢,弗洛里安

can I trust that the C compiler does modulo 2^n each time I access a bit field?
Or is there any compiler/optimisation where a code like the one below would not print out Overflow?

struct {
  uint8_t foo:2;
} G;

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

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

Thanks in Advance, Florian

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

旧情勿念 2024-10-22 19:01:12

是的,您可以相信 C 编译器会在此处执行正确的操作,只要使用 uint8_t 所具有的无符号类型声明位字段即可。来自 C99 标准 §6.2.6.1/3:

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

从 §6.7.2.1/9 开始:

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

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

有符号整数类型的非负值范围是相应无符号整数类型的子范围,并且每个类型中相同值的表示是相同的。31) A涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会以比结果类型可以表示的最大值大 1 的数为模进行缩减。

所以是的,您可以确保任何符合标准的编译器都会将 G.foo 溢出到 0,而不会产生任何其他不需要的副作用。

Yes, you can trust the C compiler to do the right thing here, as long as the bit field is declared with an unsigned type, which you have with uint8_t. From the C99 standard §6.2.6.1/3:

Values stored in unsigned bit-fields and objects of type unsigned char shall be represented using a pure binary notation.40)

From §6.7.2.1/9:

A bit-field is interpreted as a signed or unsigned integer type consisting of the specified number of bits.104) If the value 0 or 1 is stored into a nonzero-width bit-field of type _Bool, the value of the bit-field shall compare equal to the value stored.

And from §6.2.5/9 (emphasis mine):

The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.31) A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

So yes, you can be sure that any standards-conforming compiler will have G.foo overflow to 0 without any other unwanted side effects.

乖乖 2024-10-22 19:01:12

不会。编译器为该字段分配 2 位,递增 3 会得到 100b,而放入 2 位时会得到 0。

No. The compiler allocates 2 bits to the field, and incrementing 3 results in 100b, which when placed in two bits results in 0.

成熟的代价 2024-10-22 19:01:12

是的。我们可以从汇编中得到答案。
这是我在 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

Yes. We can get the answer from assembly.
Here is a example I code in Ubuntu 16.04, 64bit, 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;
}

Compile it with gcc -S <.c file>. You can get the assembly file .s. Here I show the assembly of G.foo2++;, and I write some comments.

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

We can see that compiler will use shift instructions to ensure what you say.(note: here's memory layout of G is:

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

Of course, the result of aforementioned is:

G.foo1=0x12, G.foo2=0xffffff, G=0xffffff12
G.foo1=0x12, G.foo2=0x000000, G=0x00000012
G.foo1=0x00, G.foo2=0x000000, G=0x00000000
多彩岁月 2024-10-22 19:01:12

简短的回答:是的,您可以相信模 2^n 会发生。

在你的程序中,
G.foo++; 实际上相当于 G.foo = (unsigned int)G.foo + 1

无符号整型算术总是产生 2^(无符号整型的大小(以位为单位))结果。然后,权重最小的两位存储在 G.foo 中,产生零。

Short answer: yes, you can trust modulo 2^n to happen.

In your program,
G.foo++; is in fact equivalent to G.foo = (unsigned int)G.foo + 1.

Unsigned int arithmetic always produces 2^(size of unsigned int in bits) results. The two bits of least weight are then stored in G.foo, producing zero.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文