การตัดทอนเลขจำนวนเต็มบิทฟิลด์ที่ไม่ได้ลงนามระหว่าง C ++ และ C ที่ไม่สอดคล้องกันในคอมไพเลอร์ต่างกัน


10

แก้ไข 2 :

ฉันแก้ไขข้อผิดพลาดในการทดสอบที่ผิดปกติเมื่อฟังก์ชั่นก่อนหน้านี้อาศัยอยู่ในไฟล์ต้นฉบับ C ++ แต่ย้ายไปเป็นคำต่อคำของไฟล์ C เริ่มส่งคืนผลลัพธ์ที่ไม่ถูกต้อง MVE ด้านล่างอนุญาตให้สร้างปัญหาขึ้นอีกครั้งด้วย GCC อย่างไรก็ตามเมื่อฉันตั้งใจรวบรวมตัวอย่างกับ Clang (และต่อมากับ VS) ฉันได้ผลลัพธ์ที่ต่างออกไป! ฉันไม่สามารถคิดได้ว่าจะจัดการสิ่งนี้เป็นข้อบกพร่องในคอมไพเลอร์ตัวใดตัวหนึ่งหรือเป็นการรวมตัวของผลลัพธ์ที่ไม่ได้กำหนดซึ่งอนุญาตโดยมาตรฐาน C หรือ C ++ แปลกไม่มีคอมไพเลอร์ให้คำเตือนใด ๆ เกี่ยวกับการแสดงออก

ผู้ร้ายคือการแสดงออกนี้:

ctl.b.p52 << 12;

ที่นี่p52พิมพ์เป็นuint64_t; มันยังเป็นส่วนหนึ่งของสหภาพ (ดูcontrol_tด้านล่าง) การดำเนินการกะจะไม่สูญเสียข้อมูลใด ๆ เนื่องจากผลลัพธ์ยังพอดีกับ 64 บิต อย่างไรก็ตาม GCC ตัดสินใจตัดทอนผลลัพธ์เป็น 52 บิตหากฉันใช้คอมไพเลอร์ C ! ด้วยคอมไพเลอร์ 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 แม้ว่าปัญหาดั้งเดิมของ GCC ถูกค้นพบบน Linux


1
บิตฟิลด์เป็นของปลอมสำหรับความกว้างขนาดใหญ่ ฉันเจอปัญหาที่คล้ายกันในคำถามนี้: stackoverflow.com/questions/58846584/…
chqrlie

@chqrlie ฉันอ่านโอเปอเรเตอร์C<<ตามต้องการการตัดทอน
Andrew Henle

กรุณาแสดงความ stackoverflow.com/help/minimal-reproducible-example รหัสปัจจุบันไม่มีmain.cและอาจทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดในหลายวิธี IMO จะมีความชัดเจนมากขึ้นในการโพสต์ไฟล์ MRE ไฟล์เดียวซึ่งจะสร้างเอาต์พุตที่แตกต่างกันเมื่อรวบรวมกับคอมไพเลอร์แต่ละตัว เนื่องจาก C-C ++ interop ไม่ได้ระบุไว้อย่างดีตามมาตรฐาน โปรดทราบด้วยว่าการใช้ชื่อแทนสหภาพทำให้ UB ใน C ++
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;และทำให้ประเภทของการมีauint64_t

ดังนั้นจึงปรากฏว่า GCC ถือเป็นบิตฟิลด์ใน C เป็นจำนวนเต็มชนิด 32- บิตหรือแคบกว่าถ้ามันพอดีและบิตฟิลด์ใน C ++ เป็นประเภทประกาศและไม่ปรากฏว่าเป็นการละเมิดมาตรฐาน


ฉันอ่านการตัดปลายใน C ตามคำสั่งต่อ 6.5.7 4 (ถ้อยคำ C18 คล้ายกัน): "ผลลัพธ์ของ E1 << E2 คือ E1 ตำแหน่งบิตเลื่อนไปทางซ้าย E2; บิตว่างจะเต็มไปด้วยศูนย์ถ้า E1 มีประเภทที่ไม่ได้ลงนาม ค่าของผลลัพธ์คือ E1 x 2E2 ลดโมดูโลมากกว่าค่าสูงสุดที่สามารถแสดงได้ในประเภทผลลัพธ์ " E1ในกรณีนี้คือบิตฟิลด์ 52- บิต
Andrew Henle

@AndrewHenle: ฉันเห็นสิ่งที่คุณพูด ชนิดของบิตn -bit-field คือ“ จำนวนเต็มn- bit” (ละเลยการเซ็นชื่อตอนนี้) ฉันตีความว่ามันเป็นประเภทของบิตn -bit เป็นประเภทจำนวนเต็มซึ่งการดำเนินการเลือก อ้างอิงจากถ้อยคำใน 6.7.2.1 เพียงลำพัง 10 ฉันชอบการตีความของคุณ แต่ปัญหากับการที่เป็นที่ได้รับการuint64_t a : 33กำหนดให้ 2 ^ 33-1 ในโครงสร้างsแล้วในการดำเนินการกับ C 32 บิตint, s.a+s.aควรผลผลิต 2 ^ 33-2 เนื่องจากการตัด แต่เสียงดังกราวผลิต 2 ^ 34 2; uint64_tเห็นได้ชัดว่ามันจะถือว่าเป็น
Eric Postpischil

@AndrewHenle: (เพิ่มเติมเกี่ยวกับเหตุผล: ในs.a+s.aการแปลงเลขคณิตปกติจะไม่เปลี่ยนประเภทของs.aเพราะมันกว้างกว่าunsigned intดังนั้นเลขคณิตจะทำในรูปแบบ 33 บิต)
Eric Postpischil

แต่เสียงดังดังกังวานสร้าง 2 ^ 34−2; uint64_tเห็นได้ชัดว่ามันจะถือว่าเป็น หากเป็นคอมไพล์ 64 บิตดูเหมือนว่าทำให้ Clang สอดคล้องกับวิธีที่ GCC ปฏิบัติต่อคอมไพล์ 64 บิตโดยไม่ตัดทอน เสียงดังกรังถือว่าการคอมไพล์ 32- และ 64 บิตแตกต่างกันหรือไม่? (และดูเหมือนว่าฉันเพิ่งเรียนรู้เหตุผลอื่นเพื่อหลีกเลี่ยงบิตฟิลด์ ... )
Andrew Henle

@AndrewHenle: เอาละ Clang 1.7 รุ่นเก่าสร้าง 2 ^ 32−2 (ไม่ใช่ 2 ^ 33−2; มันหายไปสักหน่อย!) ทั้งคู่ด้วย-m32และ-m64โดยมีคำเตือนว่าประเภทนั้นเป็นนามสกุล GCC ด้วย Apple Clang 11.0 ฉันไม่มีไลบรารี่ให้เรียกใช้โค้ด 32- บิต แต่แอสเซมบลีที่สร้างขึ้นแสดงpushl $3และpushl $-2ก่อนเรียกprintfดังนั้นฉันคิดว่านั่นคือ 2 ^ 34−2 ดังนั้น Apple Clang ไม่ได้แตกต่างกันระหว่างเป้าหมาย 32- บิตและ 64- บิต แต่เปลี่ยนไปตามกาลเวลา
Eric Postpischil

4

Andrew Henle แนะนำการตีความที่เข้มงวดของมาตรฐาน C: ประเภทของบิตฟิลด์คือ AA จำนวนเต็มหรือไม่ได้ลงนามประเภทที่มีความกว้างที่ระบุ

นี่คือการทดสอบที่รองรับการตีความนี้: โดยใช้โครงสร้าง C1x _Generic()ฉันพยายามกำหนดประเภทของบิตฟิลด์ที่มีความกว้างต่างกัน ฉันต้องกำหนดด้วยประเภทlong long intเพื่อหลีกเลี่ยงคำเตือนเมื่อรวบรวมด้วยเสียงดังกราว

นี่คือแหล่งที่มา:

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

นี่คือผลลัพธ์ของโปรแกรมที่คอมไพล์ด้วย clang 64 บิต:

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หรือมีบิตเขตข้อมูลที่มีความกว้างขนาดใหญ่กว่าของE2 intตัวถูกดำเนินการที่มีความกว้างขนาดเล็กกว่าจะถูกแปลงเป็นประเภทที่มีความกว้างที่ใหญ่กว่าและผลลัพธ์ก็มีประเภทประเภทด้วยเช่นกัน พฤติกรรมตอบโต้ที่ใช้งานง่ายมากซึ่งก่อให้เกิดผลลัพธ์ที่ไม่คาดคิดมากมายอาจเป็นสาเหตุของความเชื่อที่แพร่หลายว่าบิตฟิลด์นั้นเป็นของปลอมและควรหลีกเลี่ยง

ผู้แปลหลายคนดูเหมือนจะไม่ปฏิบัติตามการตีความมาตรฐาน 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- บิต) intมันไม่ควรหมายความว่ามันถูกบีบให้เป็น 32- บิตunsigned intแต่นั่นคือสิ่งที่แท้จริงอ่าน 6.3 1.1 พูดว่า
Jonathan Leffler

1
นอกจากนี้หาก C ++ ได้แก้ไขปัญหา 'ฟิลด์บิตขนาดใหญ่' อย่างชัดเจนดังนั้น C ควรปฏิบัติตามลูกค้าเป้าหมายที่ใกล้เคียงที่สุดเท่าที่จะทำได้เว้นแต่จะมีบางสิ่งที่เฉพาะเจาะจงสำหรับ C ++ เกี่ยวกับการแก้ปัญหานั้น (ซึ่งไม่น่าจะเป็นไปได้)
Jonathan Leffler

2

ปัญหาดูเหมือนจะเฉพาะเจาะจงกับตัวสร้างรหัส 32 บิตของ gcc ในโหมด C:

คุณสามารถเปรียบเทียบรหัสการประกอบโดยใช้ Compiler Explorer ของ 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.