不同编译器中C ++和C之间无符号位域整数表达式的截断不一致 [英] Inconsistent truncation of unsigned bitfield integer expressions between C++ and C in different compilers
问题描述
编辑2 :
我正在调试一个奇怪的测试失败,原因是以前驻留在C ++源文件中但完全移入C文件的函数开始返回不正确的结果.下面的MVE允许重现GCC问题.但是,当我一时兴起,用Clang(后来又用VS)编译示例时,得到了不同的结果!我无法弄清楚是将其视为其中一个编译器中的错误,还是视为C或C ++标准允许的未定义结果的体现.奇怪的是,没有一个编译器给我有关该表达式的任何警告.
I was debugging a strange test failure when a function previously residing in a C++ source file but moved into a C file verbatim, started to return incorrect results. The MVE below allows to reproduce the problem with GCC. However, when I, on a whim, compiled the example with Clang (and later with VS), I got a different result! I cannot figure out whether to treat this as a bug in one of the compilers, or as manifestation of undefined result allowed by C or C++ standard. Strangely, none of the compilers gave me any warnings about the expression.
罪魁祸首是这个表情:
ctl.b.p52 << 12;
在这里,p52
键入为uint64_t
;它也是联合的一部分(请参见下面的control_t
).移位操作不会丢失任何数据,因为结果仍然适合64位.但是,然后GCC决定将结果截断为52位如果我使用C编译器!使用C ++编译器,将保留所有64位结果.
Here, p52
is typed as uint64_t
; it is also a part of a union (see control_t
below). The shift operation does not lose any data as the result still fits into 64 bits. However, then GCC decides to truncate the result to 52 bits if I use C compiler! With C++ compiler, all 64 bits of result are preserved.
为了说明这一点,下面的示例程序用相同的主体编译了两个函数,然后比较了它们的结果. c_behavior()
放在C源文件中,cpp_behavior()
放在C ++文件中,并且main()
进行比较.
To illustrate this, the example program below compiles two functions with identical bodies, and then compares their results. c_behavior()
is placed in a C source file and cpp_behavior()
in a C++ file, and main()
does the comparison.
带有示例代码的存储库: https://github.com/grigory-rechistov /c-cpp-bitfields
Repository with the example code: https://github.com/grigory-rechistov/c-cpp-bitfields
header common.h定义64位宽位域和整数的并集,并声明两个函数:
Header common.h defines a union of 64-bit wide bitfields and integer and declares two functions:
#ifndef COMMON_H
#define COMMON_H
#include <stdint.h>
typedef union control {
uint64_t q;
struct {
uint64_t a: 1;
uint64_t b: 1;
uint64_t c: 1;
uint64_t d: 1;
uint64_t e: 1;
uint64_t f: 1;
uint64_t g: 4;
uint64_t h: 1;
uint64_t i: 1;
uint64_t p52: 52;
} b;
} control_t;
#ifdef __cplusplus
extern "C" {
#endif
uint64_t cpp_behavior(control_t ctl);
uint64_t c_behavior(control_t ctl);
#ifdef __cplusplus
}
#endif
#endif // COMMON_H
这些函数具有相同的主体,不同之处在于一个函数被视为C,另一个被视为C ++.
The functions have identical bodies, except that one is treated as C and another as C++.
c-part.c:
#include <stdint.h>
#include "common.h"
uint64_t c_behavior(control_t ctl) {
return ctl.b.p52 << 12;
}
cpp-part.cpp:
cpp-part.cpp:
#include <stdint.h>
#include "common.h"
uint64_t cpp_behavior(control_t ctl) {
return ctl.b.p52 << 12;
}
main.c:
#include <stdio.h>
#include "common.h"
int main() {
control_t ctl;
ctl.q = 0xfffffffd80236000ull;
uint64_t c_res = c_behavior(ctl);
uint64_t cpp_res = cpp_behavior(ctl);
const char *announce = c_res == cpp_res? "C == C++" : "OMG C != C++";
printf("%s\n", announce);
return c_res == cpp_res? 0: 1;
}
GCC显示了它们返回的结果之间的差异:
GCC shows the difference between the results they return:
$ gcc -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
但是,对于Clang C和C ++,它们的行为相同且符合预期:
However, with Clang C and C++ behave identically and as expected:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
在Visual Studio中,我得到的结果与在Clang中得到的结果相同:
With Visual Studio I get the same result as with Clang:
C:\Users\user\Documents>cl main.c c-part.c cpp-part.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24234.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
main.c
c-part.c
Generating Code...
Compiling...
cpp-part.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 14.00.24234.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:main.exe
main.obj
c-part.obj
cpp-part.obj
C:\Users\user\Documents>main.exe
C == C++
即使在Linux上发现了GCC的原始问题,我仍在Windows上尝试了这些示例.
I tried the examples on Windows, even though the original problem with GCC was discovered on Linux.
推荐答案
C和C ++对位字段成员的类型进行不同的处理.
C and C++ treat the types of bit-field members differently.
C 2018 6.7.2.1 10说:
C 2018 6.7.2.1 10 says:
位字段被解释为具有符号或无符号的整数类型,该整数类型由指定的位数组成……
A bit-field is interpreted as having a signed or unsigned integer type consisting of the specified number of bits…
请注意,这不是特定于类型的-它是某种整数类型-并没有说该类型是用于声明位字段的类型,如问题中的uint64_t a : 1;
所示.显然,这使实现可以选择类型.
Observe this is not specific about the type—it is some integer type—and it does not say the type is the type that was used to declare the bit-field, as in the uint64_t a : 1;
shown in the question. This apparently leaves it open to the implementation to choose the type.
C ++ 2017草案n4659 12.2.4 [class.bit] 1说,位域声明:
C++ 2017 draft n4659 12.2.4 [class.bit] 1 says, of a bit-field declaration:
...位域属性不是类成员类型的一部分...
… The bit-field attribute is not part of the type of the class member…
这意味着,在诸如uint64_t a : 1;
的声明中,: 1
不是类成员a
的类型的一部分,因此该类型就像它是uint64_t a;
一样,因此该类型a
的是uint64_t
.
This implies that, in a declaration such as uint64_t a : 1;
, the : 1
is not part of the type of the class member a
, so the type is as if it were uint64_t a;
, and thus the type of a
is uint64_t
.
因此,似乎GCC会将C中的位字段视为32位或更小的整数类型(如果合适),并将C ++中的位字段视为其声明的类型,这似乎没有违反标准.
So it appears GCC treats a bit-field in C as some integer type 32-bits or narrower if it fits and a bit-field in C++ as its declared type, and this does not appear to violate the standards.
这篇关于不同编译器中C ++和C之间无符号位域整数表达式的截断不一致的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!