我在有符号位字段上遇到了一个奇怪的行为:
#include <stdio.h>
struct S {
long long a31 : 31;
long long a32 : 32;
long long a33 : 33;
long long : 0;
unsigned long long b31 : 31;
unsigned long long b32 : 32;
unsigned long long b33 : 33;
};
long long f31(struct S *p) { return p->a31 + p->b31; }
long long f32(struct S *p) { return p->a32 + p->b32; }
long long f33(struct S *p) { return p->a33 + p->b33; }
int main() {
struct S s = { -2, -2, -2, 1, 1, 1 };
long long a32 = -2;
unsigned long long b32 = 1;
printf("f31(&s) => %lld\n", f31(&s));
printf("f32(&s) => %lld\n", f32(&s));
printf("f33(&s) => %lld\n", f33(&s));
printf("s.a31 + s.b31 => %lld\n", s.a31 + s.b31);
printf("s.a32 + s.b32 => %lld\n", s.a32 + s.b32);
printf("s.a33 + s.b33 => %lld\n", s.a33 + s.b33);
printf(" a32 + b32 => %lld\n", a32 + b32);
return 0;
}
在OS/X上使用Clang,我得到以下输出:
f31(&s) => -1
f32(&s) => 4294967295
f33(&s) => -1
s.a31 + s.b31 => 4294967295
s.a32 + s.b32 => 4294967295
s.a33 + s.b33 => -1
a32 + b32 => -1
在Linux上使用GCC,我得到了这个:
f31(&s) => -1
f32(&s) => 4294967295
f33(&s) => 8589934591
s.a31 + s.b31 => 4294967295
s.a32 + s.b32 => 4294967295
s.a33 + s.b33 => 8589934591
a32 + b32 => -1
以上输出显示了3种类型的不一致:
- 不同编译器的不同行为;
- 对于不同位字段宽度的不同行为;
- 内联表达式和函数中 Package 的等效表达式的不同行为。
C标准具有以下语言:
6.7.2类型说明符
...
每个以逗号分隔的多重集都指定相同的类型,但对于位字段,说明符int
是指定与signed int
相同的类型还是指定与unsigned int
相同的类型是由实现定义的。
众所周知,位字段在许多较旧的编译器中是损坏的...
Clang和GCC的行为是否一致,或者这些不一致是一个或多个bug的结果?
2条答案
按热度按时间4uqofj5v1#
Clang和GCC的行为是否一致,或者这些不一致是一个或多个bug的结果?
我认为最有可能的错误是在您的代码中,tbh.根据6.7.2.1p5:
比特字段的型别应该是_Bool、signed int、unsigned int或其他实作定义型别的限定或非限定版本。
这里没有提到
long long
,所以我们不一定一开始就把这段代码视为一致的。(例如,some ARM clang targets),而其他人则乐于让这种行为不被定义(例如,gcc手册似乎没有在“除_Bool,signed int,和无符号整型(C99和C11 6.7.2.1)”)。此外,根据6.3.1.1p2:
在使用int或unsigned int的表达式中,可以使用以下语句:
如果一个int可以表示原始类型的所有值(对于位字段,受宽度限制),则该值被转换为int;否则,它将转换为无符号int。
换句话说,编译器不仅要支持这些类型的位字段,还要进行适当的类型转换,这样表达式才能正确转换。具体来说,这段代码看起来 * 非常可怕 *,因为
%lld
告诉printf
要期待long long int
,而我认为您可能只传递了一个int
(或者unsigned
):我想我应该在结束时引用我对上面这个 * 毛茸茸的代码 * 的预期结果:
如果转换规范无效,则行为未定义。282)如果任何参数不是相应转换规范的正确类型,则行为未定义。
-- C11/7.21.6.1p9中的数据
doinxwow2#
请看一下建议的代码,它能正常工作并符合预期。
出于实际目的,我建议,只要确保
就这样了。
有关更多信息,请参见下面的参考文献[1]和[2]。
第一个
参考文献
[1][Signed to unsigned conversion in C - is it always safe?](https://stackoverflow.com/questions/50605/signed-to-unsigned-conversion-in-c-is-it-always-safe)
[2][https://www.geeksforgeeks.org/bit-fields-c/](https://www.geeksforgeeks.org/bit-fields-c/)“我们不能有指向位字段成员的指针,因为它们可能不在字节边界开始。”