異なるコンパイラでのC ++とCの間の符号なしビットフィールド整数式の一貫性のない切り捨て


10

編集2

以前にC ++ソースファイルに存在していた関数がそのままCファイルに移動され、誤った結果が返され始めたときに、奇妙なテストエラーをデバッグしていました。以下のMVEにより、GCCでの問題を再現できます。しかし、気まぐれでClangを使用して(そして後でVSを使用して)例をコンパイルすると、異なる結果が得られました!これをコンパイラの1つのバグとして扱うのか、CまたはC ++標準で許可されている未定義の結果の明示として扱うのか、私にはわかりません。奇妙なことに、どのコンパイラーも式に関する警告を出しませんでした。

犯人はこの表現です:

ctl.b.p52 << 12;

ここでp52は、と入力されuint64_tます。組合の一部でもありcontrol_tます(下記参照)。結果は64ビットに収まるため、シフト操作はデータを失わない。ただし、Cコンパイラを使用している場合、GCCは結果を52ビットに切り捨てることを決定します。C ++コンパイラでは、結果の64ビットがすべて保持されます。

これを説明するために、以下のサンプルプログラムは、同一の本体を持つ2つの関数をコンパイルし、それらの結果を比較します。c_behavior()Cソースファイルとcpp_behavior()C ++ファイルに配置されmain()、比較を行います。

サンプルコードを含むリポジトリ:https : //github.com/grigory-rechistov/c-cpp-bitfields

ヘッダーcommon.hは、64ビット幅のビットフィールドと整数の和集合を定義し、2つの関数を宣言します。

#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

関数の本体は同じですが、1つはCとして扱われ、もう1つは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++

LinuxでGCCの元の問題が発見されたにもかかわらず、Windowsで例を試しました。


1
ビットフィールドは、幅が広い場合に悪名高くなります。私はこの質問に同様の問題に出くわした:stackoverflow.com/questions/58846584/...
chqrlie

@chqrlie C <<演算子を切り捨てが必要であると読みました。
アンドリューヘンレ

stackoverflow.com/help/minimal-reproducible-exampleを投稿してください 。現在のコードにはmain.c、いくつかの点で未定義の動作があり、未定義の可能性があります。IMO各コンパイラでコンパイルしたときに異なる出力を生成する単一ファイルのMREをポストする方が明確です。C-C ++の相互運用性が標準で適切に指定されていないためです。また、ユニオンエイリアスはC ++でUBを引き起こすことに注意してください。
MM

@MMそうです、私が質問を投稿していたとき、それはずれました。私は今それを追加しました、そしてそれと一緒に小さなリポジトリを持つこともアイデアかもしれないと思います
Grigory Rechistov

@MM "IMO各コンパイラでコンパイルすると異なる出力を生成する単一ファイルのMREをポストする方が明確です。再生プログラムを単一のファイルに再構成します。
Grigory Rechistov

回答:


6

CおよびC ++は、ビットフィールドメンバーのタイプを異なる方法で扱います。

C 2018 6.7.2.1 10は言う:

ビットフィールドは、指定されたビット数で構成される符号付きまたは符号なし整数型として解釈されます…

これは型について特定ではなく、整数型であるuint64_t a : 1;ことに注意してください。また、質問に示されているように、型がビットフィールドの宣言に使用された型であるとは言いません。これにより、実装を開いてタイプを選択できるようになります。

C ++ 2017ドラフトn4659 12.2.4 [class.bit] 1は、ビットフィールド宣言について次のように述べています。

…ビットフィールド属性はクラスメンバーのタイプの一部ではありません…

これは、次のような宣言では、 uint64_t a : 1;: 1クラスメンバのタイプの一部ではないaので、それがあたかもタイプでありuint64_t a;、したがってのタイプaですuint64_t

したがって、GCCはCのビットフィールドを32ビット以下の整数型として適合し、C ++のビットフィールドを宣言された型として扱うように見え、これは標準に違反していないようです。


6.5.7 4(C18の表現は同様)に従って、Cでの切り捨てを必須と読みました:「E1 << E2の結果は、E1の左シフトされたE2ビット位置です。空のビットはゼロで埋められます。E1が符号なしタイプの場合、結果の値はE1 x 2E2であり、結果の型で表現可能な最大値より1を法として減じられます。」 E1この場合、52ビットのビットフィールドです。
Andrew Henle

@AndrewHenle:あなたの言っていることがわかります。nビットのビットフィールドのタイプは「nビット整数」です(現時点では符号を無視しています)。nビットのビットフィールドの型は、実装が選択する整数型であると解釈していました。6.7.2.1 10の表現のみに基づいて、私はあなたの解釈を支持します。それであるとの問題点は、所与のuint64_t a : 33構造における2 ^ 33-1へセットs、その後、32ビットとCの実装ではints.a+s.a包装に2 ^ 33-2に起因し得なければならないが、クランは^ 34- 2を生成します2; それはどうやらそれをとして扱いuint64_tます。
Eric Postpischil

@AndrewHenle:(推論の詳細:ではs.a+s.a、通常の算術変換はの型を変更しません。s.a幅がより広いunsigned intため、演算は33ビット型で行われます。)
Eric Postpischil

しかし、Clangは2 ^ 34-2を生成します。それはどうやらそれをとして扱いuint64_tます。 これが64ビットコンパイルの場合、ClangはGCCが64ビットコンパイルを切り捨てないことで処理する方法と一致しているようです。Clangは32ビットと64ビットのコンパイルを別々に扱いますか?(そして、ビットフィールドを回避する別の理由を学んだようです...)
Andrew Henle

@AndrewHenle:;の両方まあ、古いアップルクラン1.7(!それは少しを失っていない2 ^ 33-2)^ 32-2 2を生成-m32して-m64、タイプはGCCの拡張機能であることを警告して。Apple Clang 11.0では、32ビットコードを実行するためのライブラリはありませんが、生成されたアセンブリはpushl $3pushl $-2呼び出す前に表示されるprintfため、これは2 ^ 34−2だと思います。したがって、Apple Clangは32ビットと64ビットのターゲットで違いはありませんが、時間の経過とともに変化しました。
Eric Postpischil

4

Andrew Henleは、C標準の厳密な解釈を提案しました。ビットフィールドの型は、指定された幅の符号付きまたは符号なし整数型です。

これは、この解釈をサポートするテスト_Generic()です。C1x 構成を使用して、さまざまな幅のビットフィールドのタイプを特定しようとしています。long long intclangでコンパイルするときの警告を回避するために、タイプでそれらを定義する必要がありました。

ここにソースがあります:

#include <stdint.h>
#include <stdio.h>

#define typeof(X)  _Generic((X),                         \
                       long double: "long double",       \
                       double: "double",                 \
                       float: "float",                   \
                       unsigned long long int: "unsigned long long int",  \
                       long long int: "long long int",   \
                       unsigned long int: "unsigned long int",  \
                       long int: "long int",             \
                       unsigned int: "unsigned int",     \
                       int: "int",                       \
                       unsigned short: "unsigned short", \
                       short: "short",                   \
                       unsigned char: "unsigned char",   \
                       signed char: "signed char",       \
                       char: "char",                     \
                       _Bool: "_Bool",                   \
                       __int128_t: "__int128_t",         \
                       __uint128_t: "__uint128_t",       \
                       default: "other")

#define stype long long int
#define utype unsigned long long int

struct s {
    stype s1 : 1;
    stype s2 : 2;
    stype s3 : 3;
    stype s4 : 4;
    stype s5 : 5;
    stype s6 : 6;
    stype s7 : 7;
    stype s8 : 8;
    stype s9 : 9;
    stype s10 : 10;
    stype s11 : 11;
    stype s12 : 12;
    stype s13 : 13;
    stype s14 : 14;
    stype s15 : 15;
    stype s16 : 16;
    stype s17 : 17;
    stype s18 : 18;
    stype s19 : 19;
    stype s20 : 20;
    stype s21 : 21;
    stype s22 : 22;
    stype s23 : 23;
    stype s24 : 24;
    stype s25 : 25;
    stype s26 : 26;
    stype s27 : 27;
    stype s28 : 28;
    stype s29 : 29;
    stype s30 : 30;
    stype s31 : 31;
    stype s32 : 32;
    stype s33 : 33;
    stype s34 : 34;
    stype s35 : 35;
    stype s36 : 36;
    stype s37 : 37;
    stype s38 : 38;
    stype s39 : 39;
    stype s40 : 40;
    stype s41 : 41;
    stype s42 : 42;
    stype s43 : 43;
    stype s44 : 44;
    stype s45 : 45;
    stype s46 : 46;
    stype s47 : 47;
    stype s48 : 48;
    stype s49 : 49;
    stype s50 : 50;
    stype s51 : 51;
    stype s52 : 52;
    stype s53 : 53;
    stype s54 : 54;
    stype s55 : 55;
    stype s56 : 56;
    stype s57 : 57;
    stype s58 : 58;
    stype s59 : 59;
    stype s60 : 60;
    stype s61 : 61;
    stype s62 : 62;
    stype s63 : 63;
    stype s64 : 64;

    utype u1 : 1;
    utype u2 : 2;
    utype u3 : 3;
    utype u4 : 4;
    utype u5 : 5;
    utype u6 : 6;
    utype u7 : 7;
    utype u8 : 8;
    utype u9 : 9;
    utype u10 : 10;
    utype u11 : 11;
    utype u12 : 12;
    utype u13 : 13;
    utype u14 : 14;
    utype u15 : 15;
    utype u16 : 16;
    utype u17 : 17;
    utype u18 : 18;
    utype u19 : 19;
    utype u20 : 20;
    utype u21 : 21;
    utype u22 : 22;
    utype u23 : 23;
    utype u24 : 24;
    utype u25 : 25;
    utype u26 : 26;
    utype u27 : 27;
    utype u28 : 28;
    utype u29 : 29;
    utype u30 : 30;
    utype u31 : 31;
    utype u32 : 32;
    utype u33 : 33;
    utype u34 : 34;
    utype u35 : 35;
    utype u36 : 36;
    utype u37 : 37;
    utype u38 : 38;
    utype u39 : 39;
    utype u40 : 40;
    utype u41 : 41;
    utype u42 : 42;
    utype u43 : 43;
    utype u44 : 44;
    utype u45 : 45;
    utype u46 : 46;
    utype u47 : 47;
    utype u48 : 48;
    utype u49 : 49;
    utype u50 : 50;
    utype u51 : 51;
    utype u52 : 52;
    utype u53 : 53;
    utype u54 : 54;
    utype u55 : 55;
    utype u56 : 56;
    utype u57 : 57;
    utype u58 : 58;
    utype u59 : 59;
    utype u60 : 60;
    utype u61 : 61;
    utype u62 : 62;
    utype u63 : 63;
    utype u64 : 64;
} x;

int main(void) {
#define X(v)  printf("typeof(" #v "): %s\n", typeof(v))
    X(x.s1);
    X(x.s2);
    X(x.s3);
    X(x.s4);
    X(x.s5);
    X(x.s6);
    X(x.s7);
    X(x.s8);
    X(x.s9);
    X(x.s10);
    X(x.s11);
    X(x.s12);
    X(x.s13);
    X(x.s14);
    X(x.s15);
    X(x.s16);
    X(x.s17);
    X(x.s18);
    X(x.s19);
    X(x.s20);
    X(x.s21);
    X(x.s22);
    X(x.s23);
    X(x.s24);
    X(x.s25);
    X(x.s26);
    X(x.s27);
    X(x.s28);
    X(x.s29);
    X(x.s30);
    X(x.s31);
    X(x.s32);
    X(x.s33);
    X(x.s34);
    X(x.s35);
    X(x.s36);
    X(x.s37);
    X(x.s38);
    X(x.s39);
    X(x.s40);
    X(x.s41);
    X(x.s42);
    X(x.s43);
    X(x.s44);
    X(x.s45);
    X(x.s46);
    X(x.s47);
    X(x.s48);
    X(x.s49);
    X(x.s50);
    X(x.s51);
    X(x.s52);
    X(x.s53);
    X(x.s54);
    X(x.s55);
    X(x.s56);
    X(x.s57);
    X(x.s58);
    X(x.s59);
    X(x.s60);
    X(x.s61);
    X(x.s62);
    X(x.s63);
    X(x.s64);

    X(x.u1);
    X(x.u2);
    X(x.u3);
    X(x.u4);
    X(x.u5);
    X(x.u6);
    X(x.u7);
    X(x.u8);
    X(x.u9);
    X(x.u10);
    X(x.u11);
    X(x.u12);
    X(x.u13);
    X(x.u14);
    X(x.u15);
    X(x.u16);
    X(x.u17);
    X(x.u18);
    X(x.u19);
    X(x.u20);
    X(x.u21);
    X(x.u22);
    X(x.u23);
    X(x.u24);
    X(x.u25);
    X(x.u26);
    X(x.u27);
    X(x.u28);
    X(x.u29);
    X(x.u30);
    X(x.u31);
    X(x.u32);
    X(x.u33);
    X(x.u34);
    X(x.u35);
    X(x.u36);
    X(x.u37);
    X(x.u38);
    X(x.u39);
    X(x.u40);
    X(x.u41);
    X(x.u42);
    X(x.u43);
    X(x.u44);
    X(x.u45);
    X(x.u46);
    X(x.u47);
    X(x.u48);
    X(x.u49);
    X(x.u50);
    X(x.u51);
    X(x.u52);
    X(x.u53);
    X(x.u54);
    X(x.u55);
    X(x.u56);
    X(x.u57);
    X(x.u58);
    X(x.u59);
    X(x.u60);
    X(x.u61);
    X(x.u62);
    X(x.u63);
    X(x.u64);

    return 0;
}

以下は、64ビットclangでコンパイルされたプログラムの出力です。

typeof(x.s1): long long int
typeof(x.s2): long long int
typeof(x.s3): long long int
typeof(x.s4): long long int
typeof(x.s5): long long int
typeof(x.s6): long long int
typeof(x.s7): long long int
typeof(x.s8): long long int
typeof(x.s9): long long int
typeof(x.s10): long long int
typeof(x.s11): long long int
typeof(x.s12): long long int
typeof(x.s13): long long int
typeof(x.s14): long long int
typeof(x.s15): long long int
typeof(x.s16): long long int
typeof(x.s17): long long int
typeof(x.s18): long long int
typeof(x.s19): long long int
typeof(x.s20): long long int
typeof(x.s21): long long int
typeof(x.s22): long long int
typeof(x.s23): long long int
typeof(x.s24): long long int
typeof(x.s25): long long int
typeof(x.s26): long long int
typeof(x.s27): long long int
typeof(x.s28): long long int
typeof(x.s29): long long int
typeof(x.s30): long long int
typeof(x.s31): long long int
typeof(x.s32): long long int
typeof(x.s33): long long int
typeof(x.s34): long long int
typeof(x.s35): long long int
typeof(x.s36): long long int
typeof(x.s37): long long int
typeof(x.s38): long long int
typeof(x.s39): long long int
typeof(x.s40): long long int
typeof(x.s41): long long int
typeof(x.s42): long long int
typeof(x.s43): long long int
typeof(x.s44): long long int
typeof(x.s45): long long int
typeof(x.s46): long long int
typeof(x.s47): long long int
typeof(x.s48): long long int
typeof(x.s49): long long int
typeof(x.s50): long long int
typeof(x.s51): long long int
typeof(x.s52): long long int
typeof(x.s53): long long int
typeof(x.s54): long long int
typeof(x.s55): long long int
typeof(x.s56): long long int
typeof(x.s57): long long int
typeof(x.s58): long long int
typeof(x.s59): long long int
typeof(x.s60): long long int
typeof(x.s61): long long int
typeof(x.s62): long long int
typeof(x.s63): long long int
typeof(x.s64): long long int
typeof(x.u1): unsigned long long int
typeof(x.u2): unsigned long long int
typeof(x.u3): unsigned long long int
typeof(x.u4): unsigned long long int
typeof(x.u5): unsigned long long int
typeof(x.u6): unsigned long long int
typeof(x.u7): unsigned long long int
typeof(x.u8): unsigned long long int
typeof(x.u9): unsigned long long int
typeof(x.u10): unsigned long long int
typeof(x.u11): unsigned long long int
typeof(x.u12): unsigned long long int
typeof(x.u13): unsigned long long int
typeof(x.u14): unsigned long long int
typeof(x.u15): unsigned long long int
typeof(x.u16): unsigned long long int
typeof(x.u17): unsigned long long int
typeof(x.u18): unsigned long long int
typeof(x.u19): unsigned long long int
typeof(x.u20): unsigned long long int
typeof(x.u21): unsigned long long int
typeof(x.u22): unsigned long long int
typeof(x.u23): unsigned long long int
typeof(x.u24): unsigned long long int
typeof(x.u25): unsigned long long int
typeof(x.u26): unsigned long long int
typeof(x.u27): unsigned long long int
typeof(x.u28): unsigned long long int
typeof(x.u29): unsigned long long int
typeof(x.u30): unsigned long long int
typeof(x.u31): unsigned long long int
typeof(x.u32): unsigned long long int
typeof(x.u33): unsigned long long int
typeof(x.u34): unsigned long long int
typeof(x.u35): unsigned long long int
typeof(x.u36): unsigned long long int
typeof(x.u37): unsigned long long int
typeof(x.u38): unsigned long long int
typeof(x.u39): unsigned long long int
typeof(x.u40): unsigned long long int
typeof(x.u41): unsigned long long int
typeof(x.u42): unsigned long long int
typeof(x.u43): unsigned long long int
typeof(x.u44): unsigned long long int
typeof(x.u45): unsigned long long int
typeof(x.u45): unsigned long long int
typeof(x.u46): unsigned long long int
typeof(x.u47): unsigned long long int
typeof(x.u48): unsigned long long int
typeof(x.u49): unsigned long long int
typeof(x.u50): unsigned long long int
typeof(x.u51): unsigned long long int
typeof(x.u52): unsigned long long int
typeof(x.u53): unsigned long long int
typeof(x.u54): unsigned long long int
typeof(x.u55): unsigned long long int
typeof(x.u56): unsigned long long int
typeof(x.u57): unsigned long long int
typeof(x.u58): unsigned long long int
typeof(x.u59): unsigned long long int
typeof(x.u60): unsigned long long int
typeof(x.u61): unsigned long long int
typeof(x.u62): unsigned long long int
typeof(x.u63): unsigned long long int
typeof(x.u64): unsigned long long int

すべてのビットフィールドは、定義された幅に固有のタイプではなく、定義されたタイプを持っているようです。

以下は、64ビットgccでコンパイルされたプログラムの出力です。

typestr(x.s1): other
typestr(x.s2): other
typestr(x.s3): other
typestr(x.s4): other
typestr(x.s5): other
typestr(x.s6): other
typestr(x.s7): other
typestr(x.s8): signed char
typestr(x.s9): other
typestr(x.s10): other
typestr(x.s11): other
typestr(x.s12): other
typestr(x.s13): other
typestr(x.s14): other
typestr(x.s15): other
typestr(x.s16): short
typestr(x.s17): other
typestr(x.s18): other
typestr(x.s19): other
typestr(x.s20): other
typestr(x.s21): other
typestr(x.s22): other
typestr(x.s23): other
typestr(x.s24): other
typestr(x.s25): other
typestr(x.s26): other
typestr(x.s27): other
typestr(x.s28): other
typestr(x.s29): other
typestr(x.s30): other
typestr(x.s31): other
typestr(x.s32): int
typestr(x.s33): other
typestr(x.s34): other
typestr(x.s35): other
typestr(x.s36): other
typestr(x.s37): other
typestr(x.s38): other
typestr(x.s39): other
typestr(x.s40): other
typestr(x.s41): other
typestr(x.s42): other
typestr(x.s43): other
typestr(x.s44): other
typestr(x.s45): other
typestr(x.s46): other
typestr(x.s47): other
typestr(x.s48): other
typestr(x.s49): other
typestr(x.s50): other
typestr(x.s51): other
typestr(x.s52): other
typestr(x.s53): other
typestr(x.s54): other
typestr(x.s55): other
typestr(x.s56): other
typestr(x.s57): other
typestr(x.s58): other
typestr(x.s59): other
typestr(x.s60): other
typestr(x.s61): other
typestr(x.s62): other
typestr(x.s63): other
typestr(x.s64): long long int
typestr(x.u1): other
typestr(x.u2): other
typestr(x.u3): other
typestr(x.u4): other
typestr(x.u5): other
typestr(x.u6): other
typestr(x.u7): other
typestr(x.u8): unsigned char
typestr(x.u9): other
typestr(x.u10): other
typestr(x.u11): other
typestr(x.u12): other
typestr(x.u13): other
typestr(x.u14): other
typestr(x.u15): other
typestr(x.u16): unsigned short
typestr(x.u17): other
typestr(x.u18): other
typestr(x.u19): other
typestr(x.u20): other
typestr(x.u21): other
typestr(x.u22): other
typestr(x.u23): other
typestr(x.u24): other
typestr(x.u25): other
typestr(x.u26): other
typestr(x.u27): other
typestr(x.u28): other
typestr(x.u29): other
typestr(x.u30): other
typestr(x.u31): other
typestr(x.u32): unsigned int
typestr(x.u33): other
typestr(x.u34): other
typestr(x.u35): other
typestr(x.u36): other
typestr(x.u37): other
typestr(x.u38): other
typestr(x.u39): other
typestr(x.u40): other
typestr(x.u41): other
typestr(x.u42): other
typestr(x.u43): other
typestr(x.u44): other
typestr(x.u45): other
typestr(x.u46): other
typestr(x.u47): other
typestr(x.u48): other
typestr(x.u49): other
typestr(x.u50): other
typestr(x.u51): other
typestr(x.u52): other
typestr(x.u53): other
typestr(x.u54): other
typestr(x.u55): other
typestr(x.u56): other
typestr(x.u57): other
typestr(x.u58): other
typestr(x.u59): other
typestr(x.u60): other
typestr(x.u61): other
typestr(x.u62): other
typestr(x.u63): other
typestr(x.u64): unsigned long long int

これは、幅が異なるタイプの各幅と一致しています。

式はE1 << E2任意の幅未満ので、左のオペランド昇格の種類があるINT_WIDTHに昇格さintを経由して整数プロモーションと比べて任意の幅も大きくINT_WIDTH放置されています。この幅が次の値より大きい場合、式の結果は実際にビットフィールドの幅に切り捨てられる必要がありますINT_WIDTH。より正確には、符号なしの型の場合は切り捨てられる必要があり、符号付きの型に対して定義された実装である可能性があります。

またはの幅より大きいビットフィールドであるE1 + E2場合、E1または他の算術演算子についても同じことが発生します。幅の狭いオペランドは幅の広いタイプに変換され、結果もタイプタイプになります。多くの予期しない結果を引き起こすこの非常に直観に反する動作は、ビットフィールドが偽物であり、回避されるべきであると広く信じられている原因である可能性があります。E2int

多くのコンパイラは、C標準のこの解釈に従っていないようであり、この解釈が現在の表現から明らかではありません。C標準の将来のバージョンでは、ビットフィールドオペランドを含む算術演算のセマンティクスを明確にすることが役立つでしょう。


1
重要な用語は「整数プロモーション」だと思います。整数プロモーション(C11とビットフィールドの議論§6.3.1.1 - 場合はint、ビットフィールドの幅によって制限されるよう、オリジナルタイプ(のすべての値を表すことができる)、値に変換されint、それ以外の場合変換され、unsigned intこれらは整数プロモーションと呼ばれる。。 - §6.3.1.8§6.7.2.1)、ビットフィールドの幅がより広い場合をカバーしていませんint
Jonathan Leffler

1
標準が未定義のまま(せいぜい実装定義)でintunsigned intと以外のビットフィールドに許可されるタイプを許可することは役に立ちません_Bool
Jonathan Leffler

1
「任意の幅未満で32」、「32より任意の幅より大きい」及び「この幅が32より大きければ」普通のビット数が反映おそらくべきint固定32されない
ベンフォークト

1
C標準に(見落としの)問題があることに同意します。規格はuint64_tビットフィールドの使用を認可していないため、規格はそれらについて何も述べる必要がないと主張する余地があるかもしれません—動作の実装定義部分の実装のドキュメントでカバーする必要がありますビットフィールドの。特に、ビットフィールドの52ビットが(32ビット)intに収まらないからといって、32ビットに切り詰められているわけではありませんが、それはunsigned int、文字通り6.3を読み取ることです。 1.1は言う。
Jonathan Leffler

1
また、C ++が「ビッグビットフィールド」の問題を明示的に解決した場合、Cはそのリードについてできる限り厳密にそのリードに従う必要があります。
ジョナサンレフラー

2

この問題は、Cモードのgccの32ビットコードジェネレーターに固有のようです。

Godboltのコンパイラエクスプローラを使用してアセンブリコードを比較できます

このテストのソースコードは次のとおりです。

#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;

uint64_t test(control_t ctl) {
    return ctl.b.p52 << 12;
}

Cモードでの出力(フラグ-xc -O2 -m32

test:
        push    esi
        push    ebx
        mov     ebx, DWORD PTR [esp+16]
        mov     ecx, DWORD PTR [esp+12]
        mov     esi, ebx
        shr     ebx, 12
        shr     ecx, 12
        sal     esi, 20
        mov     edx, ebx
        pop     ebx
        or      esi, ecx
        mov     eax, esi
        shld    edx, esi, 12
        pop     esi
        sal     eax, 12
        and     edx, 1048575
        ret

問題は、and edx, 1048575最上位の12ビットをクリップする最後の命令です。

C ++モードでの出力は、最後の命令を除いて同じです。

test(control):
        push    esi
        push    ebx
        mov     ebx, DWORD PTR [esp+16]
        mov     ecx, DWORD PTR [esp+12]
        mov     esi, ebx
        shr     ebx, 12
        shr     ecx, 12
        sal     esi, 20
        mov     edx, ebx
        pop     ebx
        or      esi, ecx
        mov     eax, esi
        shld    edx, esi, 12
        pop     esi
        sal     eax, 12
        ret

64ビットモードでの出力は、はるかに単純で正確ですが、CコンパイラとC ++コンパイラでは異なります。

#C code:
test:
        movabs  rax, 4503599627366400
        and     rax, rdi
        ret

# C++ code:
test(control):
        mov     rax, rdi
        and     rax, -4096
        ret

バグレポートはgccバグトラッカーに提出する必要があります。


私の実験は64ビットターゲットに対してのみでしたが、32ビットの場合はさらに奇妙です。バグレポートの期限だと思います。まず、入手可能な最新のGCCバージョンで再確認する必要があります。
Grigory Rechistov

1
@GrigoryRechistov C標準の表現を考えると、バグは64ビットのターゲットが52ビットに結果を切り捨てることに失敗している可能性があります。私は個人的にそれをそのように見ています。
Andrew Henle
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.