Farklı derleyicilerde C ++ ve C arasındaki işaretsiz bitfield tamsayı ifadelerinin tutarsız kesilmesi


10

Düzenle 2 :

Bir işlev daha önce bir C ++ kaynak dosyasında bulunan ancak bir C dosyasına kelimesi kelimesine taşındığında, yanlış sonuçlar döndürmeye başladığında garip bir test hatası ayıklama. Aşağıdaki MVE, sorunun GCC ile yeniden üretilmesine izin verir. Ancak, bir hevesle, örneği Clang ile (ve daha sonra VS ile) derlediğimde, farklı bir sonuç aldım! Bunu derleyicilerden birinde hata olarak mı yoksa C veya C ++ standardı tarafından izin verilen tanımsız sonucun tezahürü olarak mı değerlendireceğim. Garip bir şekilde, hiçbir derleyici ifade hakkında bana herhangi bir uyarı vermedi.

Suçlu şu ifadedir:

ctl.b.p52 << 12;

Burada, p52olarak yazıldığında uint64_t; aynı zamanda birliğin bir parçasıdır (aşağıya control_tbakınız). Sonuç hala 64 bite sığdığı için, kaydırma işlemi herhangi bir veri kaybetmez. Ancak, C derleyicisini kullanırsam GCC sonucu 52 bite kesmeye karar verir ! C ++ derleyicisi ile 64 bitlik sonuç korunur.

Bunu göstermek için, aşağıdaki örnek program özdeş gövdeli iki işlevi derler ve sonuçlarını karşılaştırır. c_behavior()bir C kaynak dosyasına ve cpp_behavior()bir C ++ dosyasına yerleştirilir ve main()karşılaştırmayı yapar.

Örnek kod içeren depo: https://github.com/grigory-rechistov/c-cpp-bitfields

Header common.h, 64 bit geniş bit alanları ve tamsayı birleşimini tanımlar ve iki işlevi bildirir:

#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

İşlevlerin özdeş gövdeleri vardır, ancak biri C ve diğeri C ++ olarak ele alınır.

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, geri döndürdükleri sonuçlar arasındaki farkı gösterir:

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

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

Bununla birlikte, Clang C ve C ++ ile aynı ve beklendiği gibi davranır:

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

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

Visual Studio ile Clang ile aynı sonucu alıyorum:

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

GCC ile ilgili asıl sorun Linux'ta bulunmasına rağmen, Windows'ta örnekleri denedim.


1
bit alanları büyük genişlikler için kötü bir şekilde sahte. Bu soruda benzer sorunlarla karşılaştım: stackoverflow.com/questions/58846584/…
chqrlie

@chqrlie okumak <<operatörü olarak gerektiren kesme.
Mart'ta Andrew Henle

Lütfen bir stackoverflow.com/help/minimal-reproducible-example gönderin . Mevcut kodun main.ctanımlanmamış bir davranışı vardır ve muhtemelen birkaç şekilde tanımlanmamış davranışa neden olur. IMO, her derleyici ile derlendiğinde farklı çıktı üreten tek dosyalı bir MRE yayınlamak daha açık olacaktır. C-C ++ birlikte çalışma standardı tarafından iyi belirlenmediğinden. Ayrıca birleşim örtüşmesinin C ++ 'da UB'ye neden olduğunu unutmayın.
MM

@MM Doğru, soruyu gönderirken kaymış. Şimdi ekledim ve ayrıca küçük bir veri havuzuna sahip olmanın da bir fikir olabileceğini düşünüyorum
Grigory Rechistov

@MM "IMO, her derleyici ile derlendiğinde farklı çıktı üreten tek dosyalı bir MRE yayınlamak daha açık olurdu." Üretim kodumu daha küçük bir şeye dönüştürürken bunu düşünmemiştim, ancak çoğaltıcıyı tek bir dosyada yeniden biçimlendirin.
Grigory Rechistov

Yanıtlar:


6

C ve C ++, bit alanı üyesi türlerine farklı davranır.

C 2018 6.7.2.1 10 diyor ki:

Bir bit alanı, belirtilen sayıda bit içeren imzalı veya imzasız bir tamsayı türüne sahip olarak yorumlanır ...

Bunun tür hakkında özel olmadığını gözlemleyin - bir tamsayı türüdür ve türün, uint64_t a : 1;soruda gösterildiği gibi, bit alanını bildirmek için kullanılan tür olduğunu söylemez . Bu görünüşe göre türü seçmek için uygulamaya açık bırakır.

C ++ 2017 taslak n4659 12.2.4 [class.bit] 1 diyor ki, bir bit alanı bildirimi:

… Bit-field özniteliği sınıf üyesi türünün bir parçası değildir…

Örneğin bu bir beyanda, anlamına gelir uint64_t a : 1;, : 1sınıf üyesi tipi bir parçası değildir atipi, sanki bu yüzden, uint64_t a;ve böylece türü aolup uint64_t.

Bu nedenle GCC, C'deki bir bit alanına, tamsayı tipi 32 bit veya daha dar olarak davranır ve C ++ 'da bildirilen tür olarak bir bit alanına davranır ve bu, standartları ihlal ettiği görülmez.


C'deki kesmeyi 6.5.7 4'te zorunlu olarak okudum (C18 ifadesi benzer): "E1 << E2'nin sonucu E1 sola kaydırılmış E2 bit konumlarıdır; boşalan bitler sıfırlarla doldurulursa. E1 imzasız bir türe sahipse , sonucun değeri E1 x 2E2, modulo sonuç türünde gösterilebilecek maksimum değerden bir kat daha fazla. " E1bu durumda 52 bitlik bir bit alanıdır.
Mart'ta Andrew Henle

@AndrewHenle: Ne dediğini anlıyorum. Bir tür , n bitlik bit alanı “bir N (şimdilik signedness ihmal edilerek) -bit tamsayı”. Ben bunu bir n -bit bit alanı türü olarak uygulamanın seçtiği bazı tamsayı türü olarak yorumluyordum . Yalnızca 6.7.2.1 10'daki ifadelere dayanarak, yorumunuzu destekliyorum. Fakat bu bir sorun olduğu, bir verilen uint64_t a : 33bir yapıda 2 ^ 33-1 için grubu s, daha sonra, 32-bit, bir Cı-uygulanmasında int, s.a+s.abağlı ambalaja ^ 33-2 2 verim, ama Clang 2 ^ 34-üretir 2; görünüşe göre ona davranır uint64_t.
Eric Postpischil

@AndrewHenle: (Akıl yürütme hakkında daha fazla bilgi: Genelde s.a+s.a, olağan aritmetik dönüşümlerin türü s.adaha geniş olmadığı için türünü değiştirmez , bu unsigned intnedenle aritmetik 33 bit türünde yapılır.)
Eric Postpischil

ancak Clang 2 ^ 34−2 üretir; görünüşe göre ona davranır uint64_t. Bu 64 bitlik bir derlemeyse, Clang'ın GCC'nin 64 bitlik derleyicileri kısaltarak nasıl işlediğiyle tutarlı olmasını sağlıyor gibi görünüyor. Clang 32 ve 64 bitlik derleyicilere farklı davranıyor mu? (Ve bit alanlarından kaçınmak için başka bir neden öğrendim ...)
Andrew Henle

(!; O biraz kayıp değil 2 ^ 33-2): @AndrewHenle hem Well, eski Apple Clang 1.7 2 ^ 32-2 üreten -m32ve -m64tipi bir GCC olduğuna dair bir uyarı ile. Apple Clang 11.0 ile 32-bit kod çalıştırmak için kütüphaneler yok, ama oluşturulan derleme gösterir pushl $3ve pushl $-2aramadan önce printf, bu yüzden 2 ^ 34−2 olduğunu düşünüyorum. Bu yüzden Apple Clang 32 bit ve 64 bit hedefler arasında farklı değil, zamanla değişti.
Eric Postpischil

4

Andrew Henle, C Standardının katı bir yorumunu önerdi: bir bit alanı türü, tam olarak belirtilen genişliğe sahip imzalı veya imzasız bir tamsayı türüdür.

İşte bu yorumu destekleyen bir test: C1x _Generic()yapısını kullanarak, farklı genişlikteki bit alanlarının türünü belirlemeye çalışıyorum. long long intClang ile derlerken uyarıları önlemek için onları tür ile tanımlamak zorunda kaldım .

İşte kaynak:

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

Programın çıktısı 64 bit clang ile derlenmiştir:

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üm bit alanları, tanımlanan genişliğe özgü bir türden ziyade tanımlı türe sahip gibi görünüyor.

Programın çıktısı 64 bit gcc ile derlenmiştir:

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

Farklı bir türe sahip her genişlik ile tutarlı olan.

İfade E1 << E2 için herhangi bir genişlik az, yani sol işlenen terfi türünü sahip INT_WIDTHyükseltildikten intile tamsayıdır promosyon ve dışında bir genişliğe INT_WIDTHyalnız bırakılmıştır. Bu genişlik daha büyükse, ifadenin sonucu gerçekten de bit alanının genişliğine kesilmelidir INT_WIDTH. Daha kesin olarak, imzasız bir tür için kısaltılmalıdır ve imzalı türler için tanımlanmış olabilir.

İçin gerçekleşmelidir aynı E1 + E2ve diğer aritmetik operatörler halinde E1ya da E2daha büyük bir genişliğe sahip bit alanları vardır int. Daha küçük genişliğe sahip işlenen, daha geniş genişliğe sahip türe dönüştürülür ve sonuçta da tür türü bulunur. Pek çok beklenmedik sonuca neden olan bu çok sezgisel davranış, bit alanlarının sahte olduğu ve kaçınılması gerektiği yönündeki yaygın inancın nedeni olabilir.

Birçok derleyici C Standardının bu yorumuna uymuyor gibi görünmüyor ve bu yorum mevcut ifadelerden de belli değil. C Standardının gelecekteki bir versiyonunda bit-alan işlenenleri içeren aritmetik işlemlerin anlambiliminin açıklığa kavuşturulması faydalı olacaktır.


1
Bence anahtar terim 'tamsayı tanıtımları'. Tamsayı yükseltmeli bit alanlarının tartışılması (C11 §6.3.1.1 - Bir int, orijinal türün tüm değerlerini temsil edebiliyorsa (bir bit alanı için genişlikle sınırlandırıldığı gibi), değer bir değerine dönüştürülür int; aksi takdirde, bir dönüştürülür unsigned intBu tamsayı promosyonlar olarak adlandırılır.. - §6.3.1.8 , §6.7.2.1 bir bit alanı genişliğinin bir daha geniş olduğu durumda kapsamaz) int.
Jonathan Leffler

1
Standardın int, unsigned intve dışındaki bit alanları için hangi türlere izin verildiğini tanımsız (en iyi uygulama tanımlı) bırakmasına yardımcı olmaz _Bool.
Jonathan Leffler

1
"32'den küçük herhangi bir genişlik", "32'den büyük herhangi bir genişlik" ve "bu genişlik 32'den büyükse" büyük olasılıkla düz bit sayılarını yansıtmalı intve sabit bir 32 olmamalıdır.
Ben Voigt

1
C standardında bir sorun (gözetim) olduğunu kabul ediyorum. Standart, uint64_tbit alanlarının kullanımını onaylamadığından, standardın bunlar hakkında bir şey söylemek zorunda olmadığını iddia etmek için yer olabilir - bu, uygulamanın davranışın uygulama tanımlı bölümlerine ait belgelerine dahil edilmelidir. bit alanlarının. Özellikle, bit alanının 52 bitinin (32 bit) sığmaması, 32 bit intiçine sıkıştırıldıkları anlamına gelmemelidir unsigned int, ancak 6.3'ün gerçek bir okuması budur. 1.1 diyor.
Jonathan Leffler

1
Ayrıca, C ++ 'büyük bit alanı' sorunlarını açıkça çözdüyse, bu çözüm hakkında doğal olarak C ++ 'a özgü bir şey olmadığı sürece (olası olmayan) C, olası müşteriyi mümkün olduğunca yakından izlemelidir.
Jonathan Leffler

2

Sorun, ccc'deki gcc'nin 32 bit kod üreticisine özgü gibi görünüyor:

Derleme kodunu Godbolt'un Derleyici Gezgini'ni kullanarak karşılaştırabilirsiniz

Bu test için kaynak kodu şöyledir:

#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 modunda çıktı (bayraklar -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

Sorun, and edx, 1048575en önemli 12 biti kesen son talimattır .

C ++ modundaki çıktı, son talimat dışında aynıdır:

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 bit modundaki çıkış çok daha basit ve doğrudur, ancak C ve C ++ derleyicileri için farklıdır:

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

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

Gcc hata izleyicisine bir hata raporu göndermelisiniz.


Denemelerim sadece 64 bit hedefler içindi, ancak 32 bit durumunuz daha da bisarre. Sanırım bir hata raporu var. İlk olarak, benim için mevcut olan en son GCC sürümünü yeniden kontrol etmem gerekiyor.
Grigory Rechistov

1
@GrigoryRechistov kelime kullanımındaki Verilen C standardına , hata çok iyi 64 bit hedef olabileceğini başarısız 52 bite aşağı sonucu kesecek. Şahsen bu şekilde görürdüm.
Andrew Henle
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.