我可以相信C编译器每次访问位字段时都会执行模2 ^ n操作吗?还是有任何编译器/优化,像下面的代码一样不会输出Overflow?
struct {
uint8_t foo:2;
} G;
G.foo = 3;
G.foo++;
if(G.foo == 0) {
printf("Overflow\n");
}
提前感谢,Florian
我可以相信C编译器每次访问位字段时都会执行模2 ^ n操作吗?还是有任何编译器/优化,像下面的代码一样不会输出Overflow?
struct {
uint8_t foo:2;
} G;
G.foo = 3;
G.foo++;
if(G.foo == 0) {
printf("Overflow\n");
}
提前感谢,Florian
uint8_t
。从C99标准§6.2.6.1/3可知:
从§6.7.2.1/9可知:存储在无符号位域和类型为unsigned char的对象中的值应使用纯二进制表示法。40)
而从§6.2.5/9中得知(我加了强调):位域被解释为由指定位数组成的带符号或无符号整数类型。104)如果将值0或1存储到类型为
_Bool
的非零宽度位域中,则该位域的值应与存储的值相等。
所以,任何符合标准的编译器都会使有符号整数类型的非负值范围是相应无符号整数类型的子范围,且每种类型中同一值的表示方式相同。31) 由无符号操作数计算的计算永远不会溢出,因为不能由结果无符号整数类型表示的结果将对能够由结果类型表示的最大值加一取模。
G.foo
溢出为0,而没有其他不良影响。不行。编译器为该字段分配了2个比特,并将3递增得到100b,当放置在两个比特中时,结果为0。
是的。我们可以从汇编中得到答案。 这是我在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文件>
编译它。你可以得到汇编文件.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
----------------------------------
| 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
简短回答:是的,您可以相信模 2^n 的结果。
在您的程序中,
G.foo++;
实际上等同于 G.foo = (unsigned int)G.foo + 1
。
无符号整数运算总是产生 2^(无符号整数位数) 的结果。最低位的两个比特位然后存储在 G.foo
中,产生零。
uint8_t
会被提升为 int
,而不是 unsigned int
。G.foo++;
等同于 G.foo = ((int)G.foo + 1) % 4;
。 - R.. GitHub STOP HELPING ICE