Pemotongan yang tidak konsisten dari ekspresi integer bitfield yang tidak ditandatangani antara C ++ dan C dalam kompiler yang berbeda

10

Edit 2 :

Saya sedang men-debug kegagalan pengujian aneh ketika fungsi yang sebelumnya berada di file sumber C ++ tetapi pindah ke file C kata demi kata, mulai mengembalikan hasil yang salah. MVE di bawah ini memungkinkan untuk mereproduksi masalah dengan GCC. Namun, ketika saya, dengan hati-hati, mengkompilasi contoh dengan Dentang (dan kemudian dengan VS), saya mendapat hasil yang berbeda! Saya tidak tahu apakah akan memperlakukan ini sebagai bug di salah satu kompiler, atau sebagai manifestasi dari hasil yang tidak ditentukan yang diizinkan oleh standar C atau C ++. Anehnya, tidak ada kompiler yang memberi saya peringatan tentang ekspresi.

Pelakunya adalah ungkapan ini:

ctl.b.p52 << 12;

Di sini, p52diketik sebagai uint64_t; itu juga merupakan bagian dari persatuan (lihat di control_tbawah). Operasi shift tidak kehilangan data apa pun karena hasilnya masih sesuai dengan 64 bit. Namun, kemudian GCC memutuskan untuk memotong hasilnya menjadi 52 bit jika saya menggunakan kompiler C ! Dengan kompiler C ++, semua 64 bit hasil dipertahankan.

Untuk menggambarkan ini, contoh program di bawah ini mengkompilasi dua fungsi dengan tubuh yang identik, dan kemudian membandingkan hasilnya. c_behavior()ditempatkan dalam file sumber C dan cpp_behavior()dalam file C ++, dan main()melakukan perbandingan.

Repositori dengan kode contoh: https://github.com/grigory-rechistov/c-cpp-bitfields

Header common.h mendefinisikan gabungan bitfield lebar dan integer 64-bit dan mendeklarasikan dua fungsi:

#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

Fungsi-fungsinya memiliki tubuh yang identik, kecuali bahwa satu diperlakukan sebagai C dan yang lainnya sebagai 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 menunjukkan perbedaan antara hasil yang mereka kembalikan:

$ gcc -Wpedantic main.c c-part.c cpp-part.cpp

$ ./a.exe
OMG C != C++

Namun, dengan Dentang C dan C ++ berperilaku identik dan seperti yang diharapkan:

$ clang -Wpedantic main.c c-part.c cpp-part.cpp

$ ./a.exe
C == C++

Dengan Visual Studio saya mendapatkan hasil yang sama dengan Dentang:

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++

Saya mencoba contoh-contoh di Windows, meskipun masalah asli dengan GCC ditemukan di Linux.

Grigory Rechistov
sumber
1
bit-field terkenal palsu untuk lebar yang besar. Saya menemukan masalah serupa dalam pertanyaan ini: stackoverflow.com/questions/58846584/…
chqrlie
@chqrlie Saya membaca C <<Operator sebagai membutuhkan pemotongan.
Andrew Henle
Silakan kirim stackoverflow.com/help/minimal-reproducible-example . Kode saat ini tidak memiliki main.cdan mungkin menyebabkan perilaku tidak terdefinisi dalam beberapa cara. IMO akan lebih jelas untuk memposting satu file MRE yang menghasilkan output berbeda ketika dikompilasi dengan masing-masing kompiler. Karena C-C ++ interop tidak ditentukan dengan baik oleh standar. Juga perhatikan bahwa aliasing serikat menyebabkan UB dalam C ++.
MM
@ MM Benar, sudah terpeleset ketika saya memposting pertanyaan. Saya telah menambahkannya sekarang, dan saya pikir memiliki repositori kecil dengannya juga mungkin sebuah ide
Grigory Rechistov
@ MM "IMO akan lebih jelas untuk memposting satu file MRE yang menghasilkan output yang berbeda ketika dikompilasi dengan masing-masing kompiler." memformulasikan ulang reproduksi menjadi satu file.
Grigory Rechistov

Jawaban:

6

C dan C ++ memperlakukan jenis anggota bit-field secara berbeda.

C 2018 6.7.2.1 10 mengatakan:

Bit-field ditafsirkan sebagai memiliki tipe integer yang ditandatangani atau tidak ditandatangani yang terdiri dari jumlah bit yang ditentukan ...

Perhatikan ini bukan spesifik tentang tipe-itu adalah tipe integer-dan tidak mengatakan tipe adalah tipe yang digunakan untuk mendeklarasikan bit-field, seperti pada uint64_t a : 1; ditunjukkan dalam pertanyaan. Ini tampaknya membiarkannya terbuka pada implementasi untuk memilih jenisnya.

C ++ 2017 draft n4659 12.2.4 [class.bit] 1 mengatakan, dari deklarasi bit-field:

... Atribut bit-field bukan bagian dari tipe anggota kelas ...

Ini berarti bahwa, dalam deklarasi seperti uint64_t a : 1;, yang : 1bukan bagian dari jenis anggota kelas a, sehingga jenis ini seolah-olah itu uint64_t a;, dan dengan demikian jenis aini uint64_t.

Jadi tampaknya GCC memperlakukan bidang-bit dalam C sebagai beberapa tipe integer 32-bit atau lebih sempit jika cocok dan bidang-bit dalam C ++ sebagai tipe yang dideklarasikan, dan ini tampaknya tidak melanggar standar.

Eric Postpischil
sumber
Saya membaca pemotongan dalam C sebagai wajib per 6.5.7 4 (kata-kata C18 serupa): "Hasil dari E1 << E2 adalah E1 bergeser kiri posisi bit E2; bit yang dikosongkan diisi dengan nol. Jika E1 memiliki tipe unsigned , nilai hasilnya adalah E1 x 2E2, mengurangi modulo satu lebih dari nilai maksimum yang diwakili dalam tipe hasil. " E1dalam hal ini adalah bidang bit 52-bit.
Andrew Henle
@AndrewHenle: Saya mengerti apa yang Anda katakan. Jenis bidang bit- n -bit adalah “ n- bit integer” (mengabaikan signeness untuk saat ini). Saya menafsirkannya sebagai jenis n- bit bit-field adalah beberapa tipe integer, yang dipilih implementasi. Hanya berdasarkan pada kata-kata dalam 6.7.2.1 10, saya menyukai interpretasi Anda. Tetapi masalah dengan itu adalah bahwa, diberikan satu uint64_t a : 33set ke 2 ^ 33−1 dalam sebuah struktur s, maka, dalam implementasi C dengan 32-bit int, s.a+s.aharus menghasilkan 2 ^ 33−2 karena pembungkus, tetapi Dentang menghasilkan 2 ^ 34− 2; tampaknya memperlakukannya sebagai uint64_t.
Eric Postpischil
@AndrewHenle: (Lebih lanjut tentang alasan: Dalam s.a+s.a, konversi aritmatika yang biasa tidak akan mengubah jenis s.a, karena lebih luas dari unsigned int, sehingga aritmatika akan dilakukan dalam tipe 33-bit.)
Eric Postpischil
tapi dentang menghasilkan 2 ^ 34−2; tampaknya memperlakukannya sebagai uint64_t. Jika itu kompilasi 64-bit, itu sepertinya membuat Dentang konsisten dengan bagaimana GCC memperlakukan kompilasi 64-bit dengan tidak memotong. Apakah Dentang memperlakukan kompilasi 32- dan 64-bit berbeda? (Dan sepertinya saya baru saja belajar alasan lain untuk menghindari bidang bit ...)
Andrew Henle
@AndrewHenle: Yah, Apple Clang 1.7 tua menghasilkan 2 ^ 32−2 (bukan 2 ^ 33−2; hilang sedikit!) Baik dengan -m32dan -m64, dengan peringatan bahwa jenisnya adalah ekstensi GCC. Dengan Apple Clang 11.0, saya tidak memiliki perpustakaan untuk menjalankan kode 32-bit, tetapi perakitan yang dihasilkan menunjukkan pushl $3dan pushl $-2sebelum memanggil printf, jadi saya pikir itu adalah 2 ^ 34−2. Jadi Apple Clang tidak berbeda antara target 32-bit dan 64-bit tetapi memang berubah seiring waktu.
Eric Postpischil
4

Andrew Henle menyarankan interpretasi ketat dari Standar C: tipe bit-field adalah tipe integer yang bertanda tangan atau tidak bertanda tangan dengan lebar persis seperti yang ditentukan.

Berikut ini adalah tes yang mendukung interpretasi ini: menggunakan _Generic()konstruksi C1x , saya mencoba menentukan jenis bidang bit dengan lebar yang berbeda. Saya harus mendefinisikan mereka dengan tipe long long intuntuk menghindari peringatan ketika kompilasi dengan dentang.

Inilah sumbernya:

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

Berikut adalah output program yang dikompilasi dengan dentang 64-bit:

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

Semua bidang bit tampaknya memiliki tipe yang ditentukan dan bukan tipe yang spesifik untuk lebar yang ditentukan.

Ini adalah output program yang dikompilasi dengan 64-bit 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

Yang konsisten dengan masing-masing lebar memiliki tipe yang berbeda.

Ekspresi E1 << E2memiliki jenis operan kiri yang dipromosikan, sehingga lebar yang kurang dari INT_WIDTHdipromosikan intmelalui promosi bilangan bulat dan lebar lebih besar dari INT_WIDTHyang dibiarkan sendiri. Hasil ekspresi memang harus dipotong dengan lebar bidang-bit jika lebar ini lebih besar dariINT_WIDTH . Lebih tepatnya, itu harus dipotong untuk tipe yang tidak ditandatangani dan mungkin implementasi yang ditentukan untuk tipe yang ditandatangani.

Hal yang sama harus terjadi untuk E1 + E2dan operator aritmatika lainnya jika E1atau E2adalah bit-bidang dengan lebar lebih besar dari int. Operand dengan lebar yang lebih kecil dikonversi ke tipe dengan lebar yang lebih besar dan hasilnya memiliki tipe type juga. Perilaku yang sangat kontra-intuitif ini menyebabkan banyak hasil yang tidak terduga, mungkin menjadi penyebab kepercayaan luas bahwa bit-field adalah palsu dan harus dihindari.

Banyak kompiler tampaknya tidak mengikuti interpretasi Standar C ini, juga interpretasi ini tidak jelas dari kata-kata saat ini. Akan bermanfaat untuk memperjelas semantik operasi aritmatika yang melibatkan operan bit-field dalam versi C Standard yang akan datang.

chqrlie
sumber
1
Saya pikir istilah kuncinya adalah 'promosi integer'. Diskusi bidang-bit dengan promosi bilangan bulat (C11 §6.3.1.1 - Jika suatu intdapat mewakili semua nilai dari tipe asli (sebagaimana dibatasi oleh lebar, untuk bidang-bit), nilainya dikonversikan ke int; dikonversi menjadi unsigned int. Ini disebut promosi bilangan bulat. - §6.3.1.8 , §6.7.2.1 ), jangan tutupi kasus di mana lebar bidang bit lebih lebar dari a int.
Jonathan Leffler
1
Tidak membantu bahwa standar tidak terdefinisi (paling tidak ditentukan implementasi) jenis apa yang diizinkan untuk bidang bit selain int, unsigned intdan _Bool.
Jonathan Leffler
1
"setiap lebar kurang dari 32", "setiap lebar lebih besar dari 32" dan "jika lebar ini lebih besar dari 32" mungkin harus mencerminkan jumlah bit di dataran intdan tidak menjadi tetap 32.
Ben Voigt
1
Saya setuju bahwa ada masalah (pengawasan) dalam standar C. Mungkin ada ruang untuk berargumen bahwa karena standar tidak mendukung penggunaan uint64_tbit-bidang, standar tidak perlu mengatakan apa-apa tentang mereka - itu harus dicakup oleh dokumentasi implementasi dari bagian perilaku yang ditentukan implementasi dari perilaku. bit-bidang. Khususnya, hanya karena 52-bit dari bit-field tidak cocok dengan (32-bit), intitu tidak berarti bahwa mereka digesek menjadi 32-bit unsigned int, tapi itulah yang membaca 6,3 secara literal. 1.1 kata.
Jonathan Leffler
1
Juga, jika C ++ telah menyelesaikan masalah 'bidang bit besar' secara eksplisit, maka C harus mengikuti petunjuk itu sedekat mungkin - kecuali ada sesuatu yang secara inheren spesifik untuk C ++ tentang resolusi itu (yang tidak mungkin).
Jonathan Leffler
2

Masalahnya tampaknya khusus untuk generator kode 32-bit gcc dalam mode C:

Anda dapat membandingkan kode perakitan menggunakan Explorer Kompiler Godbolt

Berikut adalah kode sumber untuk tes ini:

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

Output dalam mode C (flags -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

Masalahnya adalah instruksi terakhir and edx, 1048575yang memotong 12 bit paling signifikan.

Output dalam mode C ++ identik kecuali untuk instruksi terakhir:

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

Output dalam mode 64-bit jauh lebih sederhana dan benar, namun berbeda untuk kompiler C dan C ++:

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

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

Anda harus mengajukan laporan bug pada pelacak bug gcc.

chqrlie
sumber
Eksperimen saya hanya untuk target 64-bit, tetapi kasing 32-bit Anda bahkan lebih bisar. Saya kira laporan bug sudah jatuh tempo. Pertama, saya perlu memeriksanya kembali pada versi GCC terbaru yang tersedia untuk saya.
Grigory Rechistov
1
@GrigoryRechistov Mengingat kata-kata dalam standar C , bug sangat mungkin menjadi target 64-bit gagal untuk memotong hasilnya menjadi 52 bit. Saya pribadi akan melihatnya seperti itu.
Andrew Henle