다른 컴파일러에서 C ++와 C 사이의 부호없는 비트 필드 정수 표현식이 일관되지 않음


10

편집 2 :

이전에 C ++ 소스 파일에 있던 함수가 C 파일로 그대로 이동하여 잘못된 결과를 반환하기 시작했을 때 이상한 테스트 실패를 디버깅했습니다. 아래의 MVE를 사용하면 GCC 문제를 재현 할 수 있습니다. 그러나 변덕스럽게 Clang (및 나중에 VS로)으로 예제를 컴파일하면 다른 결과를 얻었습니다! 컴파일러 중 하나에서 버그로 처리할지 또는 C 또는 C ++ 표준에서 허용되는 정의되지 않은 결과의 표현으로 처리할지 여부를 알 수 없습니다. 이상하게도, 어떤 컴파일러도 표현식에 대한 경고를주지 못했습니다.

범인은 다음과 같은 표현입니다.

ctl.b.p52 << 12;

여기서는 다음과 p52같이 입력됩니다 uint64_t. 또한 노동 조합의 일부이기도합니다 ( control_t아래 참조). 결과가 여전히 64 비트에 맞기 때문에 시프트 연산은 데이터를 잃지 않습니다. 그러나 GCC는 C 컴파일러를 사용하면 결과를 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++

Linux에서 GCC의 원래 문제가 발견되었지만 Windows에서 예제를 시도했습니다.


1
비트 필드는 큰 폭으로 악명 높습니다. 이 질문에서 비슷한 문제가 발생했습니다. stackoverflow.com/questions/58846584/…
chqrlie

@chqrlie I 판독 C의 <<연산자요구 잘림.
Andrew Henle

stackoverflow.com/help/minimal-reproducible-example을 게시하십시오 . 현재 코드에는 main.c여러 가지 방법 이 없으며 정의되지 않은 동작이 발생합니다. IMO 각 파일로 컴파일 할 때 다른 출력을 생성하는 단일 파일 MRE를 게시하는 것이 더 명확합니다. C-C ++ interop은 표준에서 제대로 지정되지 않았기 때문입니다. 또한 유니언 앨리어싱은 C ++에서 UB를 발생시킵니다.
MM

@ MM 맞습니다. 질문을 게시 할 때 미끄러졌습니다. 나는 지금을 추가하고, 또한 나 또한 생각 될 수 그것으로 작은 저장소를 가진 생각
그리고 리 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에 따라 C에서 잘림을 읽었습니다 (C18 표현은 비슷 함). 결과 값은 E1 x 2E2이며 결과 유형에서 표현할 수있는 최대 값보다 모듈로가 하나 더 감소했습니다. " E1이 경우 52 비트 비트 필드입니다.
Andrew Henle

@AndrewHenle : 나는 당신이 말하는 것을 봅니다. n 비트 비트 필드 의 타입 은 " n 비트 정수"이다 (현재 서명을 무시 함). 나는 그것을 n 비트 비트 필드의 유형이 정수 유형 으로 해석 하여 구현에서 선택했습니다. 6.7.2.1 10의 문구만을 기반으로, 나는 당신의 해석을 선호합니다. 그러나 문제는 즉, 주어진 인 uint64_t a : 33구조에서 2 ^ 33-1로 설정 s하고, 32 비트와 C 구현에서 int, s.a+s.a인해 포장에 ^ 33-2 (2)을 수득한다, 그러나 연타는 2 ^ 34의 생산 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

(! 그것은 조금 손실되지 2 ^ 33-2) : @AndrewHenle 모두 잘, 오래 된 애플 연타 1.7 2 ^ 32-2 생산 -m32-m64유형이 GCC 확장이라고 경고와 함께. 애플 연타 11.0으로, 나는 32 비트 코드를 실행할 수있는 라이브러리를 가지고 있지만, 어셈블리 프로그램을 생성하지 않는 pushl $3pushl $-2호출하기 전에 printf, 나는 그 2 ^ 34-2 생각 때문에. 따라서 Apple Clang은 32 비트와 64 비트 대상간에 차이가 없지만 시간이 지남에 따라 변경되었습니다.
Eric Postpischil

4

Andrew Henle은 C 표준에 대한 엄격한 해석을 제안했습니다. 비트 필드의 유형은 정확히 지정된 너비의 부호있는 정수 유형입니다.

이 해석을 지원하는 테스트는 다음과 같습니다. C1x _Generic()구성을 사용하여 다른 너비의 비트 필드 유형을 결정하려고합니다. 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_WIDTHINT_WIDTH

너비가보다 큰 비트 필드 이거나 비트 필드 인 E1 + E2경우 다른 산술 연산자에 대해서도 마찬가지 입니다 . 너비가 더 작은 피연산자가 너비가 더 큰 유형으로 변환되고 결과에 유형도 있습니다. 예상치 못한 결과가 많이 발생하는 이러한 반 직관적 인 동작은 비트 필드가 허위이므로 피해야한다는 광범위한 신념의 원인 일 수 있습니다.E1E2int

많은 컴파일러가 C 표준에 대한이 해석을 따르지 않는 것 같으며, 현재 해석에서이 해석이 명확하지 않습니다. 향후 버전의 C 표준에서 비트 필드 피연산자가 포함 된 산술 연산의 의미를 명확하게하는 것이 유용합니다.


1
핵심 용어는 '정수 프로모션'이라고 생각합니다. 정수 프로모션 비트 필드의 설명 (C11 §6.3.1.1 - 이 경우 int비트 필드의 폭에 의해 한정되는 바와 같이 원래의 형태 (), 가치가 변환되는 모든 값을 나타낼 수있다 int, 그렇지 않으면 그것을 로 변환되고 unsigned int이들은 정수 프로모션 불린다.. - §6.3.1.8을 , §6.7.2.1 비트 필드의 폭이보다 넓은 경우에는 적용되지 않는다) int.
Jonathan Leffler

1
이것은 표준 잎이 정의되지 않은 것을하지 도움을한다 (최상의 구현 정의) 이외의 비트 필드에 허용되는 어떤 종류의 int, unsigned int그리고 _Bool.
Jonathan Leffler

1
"32보다 작은 너비", "32보다 큰 너비"및 "이 너비가 32보다 큰 경우"는 아마도 비트 수를 평범하게 반영해야 int하며 고정 된 32가 아니 어야합니다 .
Ben Voigt

1
C 표준에 문제 (감시)가 있음에 동의합니다. 표준이 uint64_t비트 필드 의 사용을 승인하지 않기 때문에 표준은 그것에 대해 아무 말도 할 필요가 없습니다. 구현의 동작 정의 부분에 대한 구현 문서에 포함되어야합니다. 비트 필드 특히, 비트 필드의 52 비트가 (32 비트)에 맞지 않기 때문에 32 비트로 축소 int되었다는 의미는 unsigned int아니지만 문자 그대로 6.3을 읽습니다. 1.1이 말합니다.
Jonathan Leffler

1
또한 C ++에서 '빅 비트 필드'문제를 명시 적으로 해결 한 경우 C는 해당 해상도에 대해 본질적으로 특정 사항이없는 한 (가능한 경우가 아니라면) 가능한 한 그 리드를 따라야합니다.
Jonathan Leffler

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, 104857512 개의 최상위 비트를 클립 하는 마지막 명령 입니다.

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.