编辑2:
当以前驻留在C ++源文件中但完全移入C文件的函数开始返回不正确的结果时,我正在调试一个奇怪的测试失败。下面的MVE允许重现GCC问题。但是,当我一时兴起,用Clang(后来又用VS)编译示例时,得到了不同的结果!我无法弄清楚是将其视为编译器之一中的错误,还是C或C ++标准允许的未定义结果的体现。奇怪的是,没有一个编译器给我有关该表达式的任何警告。
罪魁祸首是这样的表达:
ctl.b.p52 << 12;
在这里,p52
键入为uint64_t
;它也是工会的一部分(见control_t
下文)。移位操作不会丢失任何数据,因为结果仍然适合64位。但是,如果我使用C编译器,那么GCC决定将结果截断为52位!使用C ++编译器,将保留所有64位结果。
为了说明这一点,下面的示例程序用相同的主体编译了两个函数,然后比较了它们的结果。c_behavior()
放在C源文件和cpp_behavior()
C ++文件中,并main()
进行比较。
带有示例代码的存储库:https : //github.com/grigory-rechistov/c-cpp-bitfields
标头common.h定义64位宽位域和整数的并集,并声明两个函数:
#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 ++。
c-part.c:
#include <stdint.h>
#include "common.h"
uint64_t c_behavior(control_t ctl) {
return ctl.b.p52 << 12;
}
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 -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
OMG C != C++
但是,对于Clang C和C ++,它们的行为相同且符合预期:
$ clang -Wpedantic main.c c-part.c cpp-part.cpp
$ ./a.exe
C == C++
使用Visual Studio,可以获得与使用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++
我在Windows上尝试了这些示例,即使在Linux上发现了GCC的原始问题。
<<
运营商为需要截断。
main.c
并且可能以多种方式导致未定义的行为。IMO会更清楚地发布一个单文件MRE,该MRE在使用每个编译器进行编译时会产生不同的输出。因为C-C ++互操作标准没有很好地指定。另请注意,联合别名会导致C ++中的UB。