不同编译器中C ++和C之间无符号位域整数表达式的截断不一致 [英] Inconsistent truncation of unsigned bitfield integer expressions between C++ and C in different compilers

查看:149
本文介绍了不同编译器中C ++和C之间无符号位域整数表达式的截断不一致的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆