Việc cắt không nhất quán các biểu thức số nguyên bit không dấu giữa C ++ và C trong các trình biên dịch khác nhau


10

Chỉnh sửa 2 :

Tôi đã gỡ lỗi một lỗi thử nghiệm lạ khi một chức năng trước đây nằm trong tệp nguồn C ++ nhưng đã chuyển sang nguyên văn tệp C, bắt đầu trả về kết quả không chính xác. MVE dưới đây cho phép tái tạo vấn đề với GCC. Tuy nhiên, khi tôi, trong một ý thích bất chợt, đã biên soạn ví dụ với Clang (và sau đó với VS), tôi đã nhận được một kết quả khác! Tôi không thể biết liệu có nên coi đây là một lỗi trong một trong các trình biên dịch hay là biểu hiện của kết quả không xác định được cho phép theo tiêu chuẩn C hoặc C ++. Kỳ lạ thay, không có trình biên dịch nào đưa ra cho tôi bất kỳ cảnh báo nào về biểu thức.

Thủ phạm chính là biểu hiện này:

ctl.b.p52 << 12;

Ở đây, p52được gõ như uint64_t; nó cũng là một phần của liên minh (xem control_tbên dưới). Hoạt động thay đổi không mất bất kỳ dữ liệu nào vì kết quả vẫn phù hợp với 64 bit. Tuy nhiên, sau đó GCC quyết định cắt kết quả thành 52 bit nếu tôi sử dụng trình biên dịch C ! Với trình biên dịch C ++, tất cả 64 bit kết quả được giữ nguyên.

Để minh họa điều này, chương trình ví dụ dưới đây biên dịch hai hàm với các phần thân giống nhau và sau đó so sánh kết quả của chúng. c_behavior()được đặt trong tệp nguồn C và cpp_behavior()trong tệp C ++ và main()thực hiện so sánh.

Kho lưu trữ với mã ví dụ: https://github.com/grigory-rechistov/c-cpp-bitfields

Tiêu đề chung.h định nghĩa một liên kết gồm các bit và số nguyên rộng 64 bit và khai báo hai hàm:

#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 hàm có các cơ thể giống hệt nhau, ngoại trừ một hàm được coi là C và một hàm khác là 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;
}

C chính:

#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 cho thấy sự khác biệt giữa kết quả họ trả về:

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

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

Tuy nhiên, với Clang C và C ++ hoạt động giống hệt và như mong đợi:

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

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

Với Visual Studio tôi nhận được kết quả tương tự như với 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++

Tôi đã thử các ví dụ trên Windows, mặc dù vấn đề ban đầu với GCC đã được phát hiện trên Linux.


1
trường bit nổi tiếng là không có thật cho chiều rộng lớn. Tôi đã gặp các vấn đề tương tự trong câu hỏi này: stackoverflow.com/questions/58846584/ trên
chqrlie

@chqrlie Tôi đọc toán tử C<< khi yêu cầu cắt ngắn.
Andrew Henle

Vui lòng đăng một stackoverflow.com/help/minimal-reproducible-example . Mã hiện tại không có main.cvà có thể gây ra hành vi không xác định theo nhiều cách. IMO sẽ rõ ràng hơn khi đăng MRE một tệp tạo ra đầu ra khác nhau khi được biên dịch với mỗi trình biên dịch. Bởi vì tiêu chuẩn C-C ++ không được chỉ định tốt bởi tiêu chuẩn. Cũng lưu ý rằng răng cưa kết hợp gây ra UB trong C ++.
MM

@MM Phải, nó đã bị trượt khi tôi đăng câu hỏi. Tôi đã thêm nó ngay bây giờ và tôi cũng nghĩ rằng có một kho lưu trữ nhỏ với nó cũng có thể là một ý tưởng
Grigory Rechistov

@MM "IMO sẽ rõ ràng hơn khi đăng MRE một tệp tạo ra đầu ra khác nhau khi được biên dịch với mỗi trình biên dịch." Tôi không nghĩ về điều đó khi tôi chuyển đổi mã sản xuất của mình thành một cái gì đó nhỏ hơn, nhưng có thể cải cách trình sao chép thành một tập tin duy nhất.
Grigory Rechistov

Câu trả lời:


6

C và C ++ đối xử với các loại thành viên trường bit khác nhau.

C 2018 6.7.2.1 10 nói:

Trường bit được hiểu là có kiểu số nguyên được ký hoặc không dấu bao gồm số bit được chỉ định.

Quan sát điều này không cụ thể về loại hình này, đó là một số loại số nguyên và nó không nói loại đó là loại được sử dụng để khai báo trường bit, như trong uint64_t a : 1;câu hỏi. Điều này rõ ràng để nó mở cho việc thực hiện để chọn loại.

Bản thảo C ++ 2017 n4659 12.2.4 [class.bit] 1 cho biết, về khai báo trường bit:

Thuộc tính bit-field không phải là một phần của loại thành viên lớp

Điều này ngụ ý rằng, trong một tuyên bố như uint64_t a : 1;, : 1không phải là một phần của loại thành viên lớp a, vì vậy loại này là như thể nó là uint64_t a;, và do đó loại auint64_t.

Vì vậy, GCC xuất hiện coi một trường bit trong C là một số loại 32 bit hoặc hẹp hơn nếu nó phù hợp và một trường bit trong C ++ như loại khai báo của nó và điều này dường như không vi phạm các tiêu chuẩn.


Tôi đọc phần rút gọn trong C là bắt buộc theo 6.5.7 4 (cách diễn đạt C18 tương tự): "Kết quả của E1 << E2 là vị trí bit E2 dịch chuyển trái; các bit bị bỏ trống được điền bằng 0. Nếu E1 có loại không dấu , giá trị của kết quả là E1 x 2E2, giảm modulo một lần so với giá trị tối đa có thể biểu thị trong loại kết quả. " E1trong trường hợp này là trường bit 52 bit.
Andrew Henle

@AndrewHenle: Tôi thấy những gì bạn đang nói. Loại một n -bit lĩnh vực -bit là “ n số nguyên -bit” (bỏ qua signedness cho bây giờ). Tôi đã giải thích nó là loại trường bit n -bit là một số loại số nguyên mà việc triển khai chọn. Chỉ dựa vào từ ngữ trong 6.7.2.1 10, tôi ủng hộ cách giải thích của bạn. Nhưng một vấn đề với điều đó là, được đặt một uint64_t a : 33bộ thành 2 ^ 33−1 trong một cấu trúc s, sau đó, trong một triển khai C với 32 bit int, s.a+s.asẽ mang lại 2 ^ 33−2 do gói, nhưng Clang tạo ra 2 ^ 34− 2; nó dường như coi nó là uint64_t.
Eric Postpischil

@AndrewHenle: (Thêm về lý do: Trong s.a+s.a, các chuyển đổi số học thông thường sẽ không thay đổi loại s.a, vì nó rộng hơn unsigned int, do đó, số học sẽ được thực hiện trong loại 33 bit.)
Eric Postpischil

nhưng Clang tạo ra 2 ^ 34−2; nó dường như coi nó là uint64_t. Nếu đó là một trình biên dịch 64 bit, điều đó dường như làm cho Clang phù hợp với cách GCC đang xử lý các biên dịch 64 bit bằng cách không cắt bớt. Clang có xử lý các biên dịch 32 và 64 bit khác nhau không? (Và dường như tôi vừa học được một lý do khác để tránh các trường bit ...)
Andrew Henle

@AndrewHenle: Chà, Apple Clang 1.7 cũ tạo ra 2 ^ 32−2 (không phải 2 ^ 33−2; nó mất một chút!) Cả với -m32-m64, với một cảnh báo rằng loại này là một phần mở rộng GCC. Với Apple Clang 11.0, tôi không có thư viện để chạy mã 32 bit, nhưng hội thảo được tạo sẽ hiển thị pushl $3pushl $-2trước khi gọi printf, vì vậy tôi nghĩ đó là 2 ^ 34−2. Vì vậy, Apple Clang không khác nhau giữa các mục tiêu 32 bit và 64 bit nhưng đã thay đổi theo thời gian.
Eric Postpischil

4

Andrew Henle đã đề xuất một cách giải thích chặt chẽ về Tiêu chuẩn C: loại trường bit là loại số nguyên được ký hoặc không dấu với độ rộng chính xác.

Đây là một thử nghiệm hỗ trợ cho việc giải thích này: sử dụng _Generic()cấu trúc C1x , tôi đang cố gắng xác định loại trường bit có độ rộng khác nhau. Tôi đã phải xác định chúng với loại long long intđể tránh cảnh báo khi biên dịch với tiếng kêu.

Đây là nguồn:

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

Đây là đầu ra của chương trình được biên dịch với tiếng kêu 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

Tất cả các trường bit dường như có loại được xác định thay vì một loại cụ thể cho chiều rộng được xác định.

Đây là đầu ra của chương trình được biên dịch với gcc 64 bit:

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

Mà phù hợp với mỗi chiều rộng có một loại khác nhau.

Biểu thức E1 << E2có loại toán hạng bên trái được quảng cáo, do đó, bất kỳ chiều rộng nào nhỏ hơn INT_WIDTHđược quảng bá intthông qua quảng cáo số nguyên và bất kỳ chiều rộng nào lớn hơn INT_WIDTHđược để lại một mình. Kết quả của biểu thức thực sự nên được cắt bớt theo chiều rộng của trường bit nếu chiều rộng này lớn hơn INT_WIDTH. Chính xác hơn, nó nên được cắt bớt cho một loại không dấu và nó có thể được thực hiện được xác định cho các loại đã ký.

Điều tương tự sẽ xảy ra đối với E1 + E2và các toán tử số học khác nếu E1hoặc E2là các trường bit có chiều rộng lớn hơn so với int. Toán hạng có chiều rộng nhỏ hơn được chuyển đổi thành loại có chiều rộng lớn hơn và kết quả cũng có loại loại. Hành vi rất phản trực quan này gây ra nhiều kết quả bất ngờ, có thể là nguyên nhân của niềm tin phổ biến rằng các trường bit là không có thật và nên tránh.

Nhiều trình biên dịch dường như không tuân theo cách giải thích này của Tiêu chuẩn C, và cách giải thích này không rõ ràng từ cách diễn đạt hiện tại. Sẽ rất hữu ích khi làm rõ ngữ nghĩa của các phép toán số học liên quan đến toán hạng trường bit trong phiên bản tương lai của Tiêu chuẩn C.


1
Tôi nghĩ thuật ngữ chính là 'khuyến mãi số nguyên'. Thảo luận về các trường bit có khuyến mãi số nguyên (C11 §6.3.1.1 - Nếu một intcó thể đại diện cho tất cả các giá trị của loại ban đầu (bị giới hạn bởi chiều rộng, đối với trường bit), giá trị được chuyển đổi thành int; được chuyển đổi thành một unsigned int. Chúng được gọi là các chương trình khuyến mãi số nguyên. - §6.3.1.8 , §6.7.2.1 ), không bao gồm trường hợp chiều rộng của trường bit rộng hơn một int.
Jonathan Leffler

1
Nó không giúp đỡ mà lá tiêu chuẩn undefined (lúc tốt nhất thực hiện xác định) những loại được phép cho bit các lĩnh vực khác hơn int, unsigned int_Bool.
Jonathan Leffler

1
"bất kỳ chiều rộng nào nhỏ hơn 32", "bất kỳ chiều rộng nào lớn hơn 32" và "nếu chiều rộng này lớn hơn 32" có lẽ sẽ phản ánh số lượng bit trong đồng bằng intvà không phải là cố định 32.
Ben Voigt

1
Tôi đồng ý rằng có một vấn đề (giám sát) trong tiêu chuẩn C. Có thể có lý lẽ để tranh luận rằng vì tiêu chuẩn không xử phạt việc sử dụng các trường uint64_tbit, nên tiêu chuẩn không phải nói bất cứ điều gì về chúng - nó phải được đề cập trong tài liệu của việc thực hiện các phần do hành vi xác định thực hiện của các trường bit. Cụ thể, chỉ vì 52 bit của trường bit không khớp với (32 bit), intđiều đó không có nghĩa là chúng bị nghiền nát thành 32 bit unsigned int, nhưng đó là cách đọc theo nghĩa đen của 6.3. 1.1 nói.
Jonathan Leffler

1
Ngoài ra, nếu C ++ đã giải quyết vấn đề 'trường bit lớn' một cách rõ ràng, thì C nên tuân theo sự dẫn dắt đó càng chặt chẽ càng tốt - trừ khi có điều gì đó vốn đã cụ thể đối với C ++ về độ phân giải đó (không có khả năng).
Jonathan Leffler

2

Vấn đề dường như là cụ thể đối với trình tạo mã 32 bit của gcc ở chế độ C:

Bạn có thể so sánh mã lắp ráp bằng Trình biên dịch trình duyệt của Godbolt

Đây là mã nguồn cho bài kiểm tra này:

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

Đầu ra ở chế độ C (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

Vấn đề là hướng dẫn cuối cùng and edx, 1048575cắt 12 bit quan trọng nhất.

Đầu ra trong chế độ C ++ giống hệt nhau ngoại trừ hướng dẫn cuối cùng:

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

Đầu ra ở chế độ 64 bit đơn giản và chính xác hơn nhiều, nhưng khác với trình biên dịch C và C ++:

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

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

Bạn nên nộp báo cáo lỗi về trình theo dõi lỗi gcc.


Các thử nghiệm của tôi chỉ dành cho các mục tiêu 64 bit, nhưng trường hợp 32 bit của bạn thậm chí còn kỳ quái hơn. Tôi đoán một báo cáo lỗi là do. Đầu tiên, tôi cần kiểm tra lại nó trên phiên bản GCC mới nhất có sẵn cho tôi.
Grigory Rechistov

1
@GrigoryRechistov Với cách diễn đạt trong tiêu chuẩn C , lỗi rất có thể là mục tiêu 64 bit không thể cắt kết quả xuống còn 52 bit. Cá nhân tôi sẽ xem nó theo cách đó.
Andrew Henle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.