Enum sabitleri C ve C ++ 'da farklı davranıyor


81

Bunu neden yapar:

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}

4 8 8C ve 8 8 8C ++ olarak yazdırın (4 baytlık bir platformda)?

UINT64_MAXAtamanın tüm numaralandırma sabitlerini en az 64 bite zorlayacağı, ancak en_e_foodüz C'de 32'de kalacağı izlenimindeydim .

Tutarsızlığın gerekçesi nedir?


1
Hangi derleyiciler? Fark eder mi bilmiyorum, ama olabilir.
Mark Ransom

@MarkRansom gcc ile geldi ama clang aynı şekilde davranıyor.
PSkocik


3
"4 bayt girişli bir platformda" Yalnızca platform değil, tür genişliklerini belirleyen derleyici de. Hepsi bu olabilir. (Keith'in cevabına göre, aslında değil, ancak genel olarak bu tür olasılıkların farkında olun)
Orbit'te Hafiflik Yarışları

1
@PSkocik: Gerçekten bir değişiklik, bu soru hem geçerli bir kullanım alanı bulmuş sadece bu değil c ve c ++ (bazı kod ikisi arasındaki farklı davranışlar neden niçin soran). Ayrıca tamam: C ++ 'dan C kitaplıklarının nasıl çağrılacağını ve C'den çağrılabilen C ++' ın nasıl yazılacağını sormak Tamam değil: bir C sorusu sormak ve "daha fazla göz alması için" C ++ etiketi atmak. Ayrıca doğru değil: C ++ sorusu sormak ve sonradan akla gelen "C'yi de yanıtladığınızdan emin olun". (ve her zamanki şikayetçiler için - pek uygun değil: C ++ etiketini C etiketine değiştirmek, çünkü kod her iki standartta da var olan işlevleri kullanıyor)
Ben Voigt

Yanıtlar:


80

C'de enumsabit tiptedir int. C ++ 'da, numaralandırılmış türdendir.

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};

C, bu bir kısıtlama ihlali (bir teşhis gerektiren eğer UINT64_MAX aşarsa INT_MAXçok muhtemelen işlevi gören). AC derleyicisi programı tamamen reddedebilir veya bir uyarı yazdırabilir ve ardından davranışı tanımsız olan bir yürütülebilir dosya oluşturabilir. (Bir kısıtlamayı ihlal eden bir programın mutlaka tanımlanmamış bir davranışa sahip olduğu% 100 net değildir, ancak bu durumda standart davranışın ne olduğunu söylemez, bu nedenle bu hala tanımlanmamış bir davranıştır.)

gcc 6.2 bu konuda uyarmaz. clang yapar. Bu gcc'deki bir hatadır; standart başlıklardan makrolar kullanıldığında bazı tanı mesajlarını hatalı şekilde engeller. Hata raporunu bulduğu için Grzegorz Szpetkowski'ye teşekkürler: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613

C ++ 'da, her numaralandırma türünün bir tamsayı türü olan (zorunlu olarak değil ) temel bir türü vardır int. Bu temel tür, tüm sabit değerleri temsil edebilmelidir. Yani bu durumda, hem en_e_foove en_e_bartiptedir en_ebile, geniş, en az 64 bit olmalıdır intdardır.


10
hızlı not: UINT64_MAXaşmamak INT_MAXiçin inten az 65 bit gerektirir .
Ben Voigt

10
Gerçekten garip olan şey, gcc (5.3.1) 'in ile -Wpedanticve 18446744073709551615ULLile değil ile bir uyarı yayınlamasıdır UINT64_MAX.
nwellnhof

4
@dascandy: Hayır, intimzalı bir tür olmalıdır, bu nedenle temsil edebilmesi için en az 65 bit olması gerekir UINT64_MAX(2 ** 64-1).
Keith Thompson

1
@KeithThompson, 6.7.2.2 "bir numaralandırıcı listesindeki tanımlayıcıların int türüne sahip sabitler olarak bildirildiğini ve izin verilen yerlerde görünebileceğini" söylüyor. Anladığım kadarıyla, tek bir C numaralandırmasının bildirdiği sabitler, numaralandırmanın türünü kullanmaz, bu nedenle, onları farklı türler haline getirmek için büyük bir uzantı yoktur (özellikle standardın bir uzantısı olarak uygulanırsa).
zneak

2
@AndrewHenle: enum'dan en_e_bardaha büyük değil en_e_foo, daha küçük. Enum değişkeni en büyük sabit kadar büyüktü.
Ben Voigt

25

Bu kod ilk etapta geçerli C değil.

Hem C99 hem de C11'deki Bölüm 6.7.2.2 şunu söylüyor:

Kısıtlamalar:

Bir numaralandırma sabitinin değerini tanımlayan ifade, bir olarak gösterilebilen bir değere sahip tamsayı sabit bir ifade olacaktır int.

Derleyici teşhisi zorunludur çünkü kısıtlama ihlali olduğu için, bkz. 5.1.1.3:

Uygun bir uygulama, bir ön işleme çeviri birimi veya çeviri birimi herhangi bir sözdizimi kuralının veya kısıtlamasının ihlalini içeriyorsa, davranış da açıkça tanımlanmamış veya uygulama olarak belirtilmiş olsa bile, en az bir tanılama mesajı (uygulama tanımlı bir şekilde tanımlanmış) üretmelidir. tanımlı.


23

In C Bir süre, enumayrı tip olarak kabul edilir, kendisi hep türü var enumerator'ler int.

C11 - 6.7.2.2 Numaralandırma tanımlayıcıları

3 Numaralandırıcı listesindeki tanımlayıcılar, int türüne sahip sabitler olarak bildirilir ...

Bu nedenle, gördüğünüz davranış bir derleyici uzantısıdır.

Değeri çok büyükse numaralandırıcılardan birinin boyutunu genişletmenin mantıklı olduğunu söyleyebilirim.


Öte yandan, C ++ 'da tüm numaralandırıcılar , içinde enumbildirildikleri türüne sahiptir .

Bu nedenle, her numaralandırıcının boyutu aynı olmalıdır. Böylece, enumen büyük numaralandırıcıyı depolamak için bütünün boyutu genişletilir.


11
Bu bir derleyici uzantısıdır, ancak tanılama üretememe bir uyumsuzluktur.
Ben Voigt

16

Diğerlerinin de belirttiği gibi, kısıtlama ihlali nedeniyle kod kötü biçimlidir (C'de).

Bazı yararlı uyarıların makrolarla susturulduğunu belirten GCC # 71613 hatası (Haziran 2016'da bildirildi) var.

Sistem üstbilgilerinden gelen makrolar kullanıldığında faydalı uyarılar susturuluyor gibi görünüyor. Örneğin, aşağıdaki örnekte bir uyarı her iki numaralandırma için de yararlı olabilir, ancak yalnızca bir uyarı gösterilir. Aynı şey muhtemelen diğer uyarılar için de olabilir.

Geçerli geçici çözüm, makroyu başına tekli +operatör eklemek olabilir :

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};

bu, GCC 4.9.2 ile makinemde derleme hatası veriyor:

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range ofint’ [-Wpedantic]
         en_e_bar = +UINT64_MAX

12

C11 - 6.7.2.2/2

Bir numaralandırma sabitinin değerini tanımlayan ifade, bir int.

en_e_bar=UINT64_MAXbir kısıtlama ihlalidir ve bu, yukarıdaki kodu geçersiz kılar. C11 taslağında belirtildiği gibi uygulama onaylanarak bir teşhis mesajı üretilmelidir:

Uygun bir uygulama, bir ön işleme çeviri birimi veya çeviri birimi herhangi bir sözdizimi kuralı veya kısıtlamasının ihlalini içeriyorsa, en az bir tanılama mesajı (uygulama tanımlı bir şekilde tanımlanmış) üretmelidir, [...]

Görünüşe göre GCC'nin bazı hataları var ve teşhis mesajını üretemedi. (Bug bakacak cevap tarafından Grzegorz Szpetkowski


8
"tanımlanmamış davranış" bir çalışma zamanı etkisidir. sizeofbir derleme zamanı operatörüdür. Burada UB yok ve olsaydı bile etkileyemezdi sizeof.
Ben Voigt

2
Bir int'e sığmayan numaralandırıcıların UB olduğu standart alıntıyı bulmalısınız. Bu ifadeye son derece şüpheliyim ve oyum bu çözülene kadar sabit -1 olarak kalacak.
zneak

3
@Sergey: C standardı aslında "Bir numaralandırma sabitinin değerini tanımlayan ifade, int olarak gösterilebilen bir değere sahip bir tamsayı sabit ifadesi olacaktır." ancak bunu ihlal etmek bir kısıtlama ihlali olur, teşhis gerekir, UB değil.
Ben Voigt

3
@haccks: Evet? Bu bir kısıtlama ihlalidir ve "Bir ön işleme çeviri birimi veya çeviri birimi herhangi bir sözdizimi kuralının veya kısıtlamasının ihlalini içeriyorsa, uygun bir uygulama en az bir tanılama mesajı (uygulama tanımlı bir şekilde tanımlanır) üretmelidir. açıkça belirtilmemiş veya uygulama tanımlı olarak belirtilmiş. "
Ben Voigt

2
Taşma ve kesme arasında bir fark var. Taşma, beklenen sonuç türü için çok büyük bir değer üreten bir aritmetik işleminiz olduğunda ve işaretli taşma UB olduğunda ortaya çıkar. Kesme, hedef türün başlaması için çok büyük bir değere sahip olduğunuzda (benzer short s = 0xdeadbeef) ve davranış uygulama tanımlı olduğunda gerçekleşir.
zneak

5

Standartlara bir göz attım ve programım 6.7.2.2p2 nedeniyle C'de bir kısıtlama ihlali gibi görünüyor :

Kısıtlamalar: Bir numaralandırma sabitinin değerini tanımlayan ifade, int olarak gösterilebilen bir değere sahip bir tamsayı sabit ifadesi olacaktır.

ve 7.2.5 nedeniyle C ++ 'da tanımlanmıştır:

Temel tür sabit değilse, her numaralandırıcının türü, başlangıç ​​değerinin türüdür: - Numaralandırıcı için bir başlatıcı belirtilmişse, başlatma değeri ifade ile aynı türe sahiptir ve sabit ifade bir integral olacaktır. sabit ifade (5.19). - İlk numaralandırıcı için başlatıcı belirtilmezse, başlatma değerinin belirtilmemiş bir integral türü vardır. - Aksi takdirde, başlatılan değerin türü önceki numaralandırıcının başlatan değerinin türü ile aynıdır, ancak artan değer bu türde gösterilemez, bu durumda tür, artırılmış değeri içermek için yeterli, belirtilmemiş bir integral türüdür. Böyle bir tür yoksa, program kötü biçimlendirilmiştir.


3
C'de "tanımsız" değil, "biçimsiz" çünkü bir kısıtlama ihlal ediliyor. Derleyici, ihlalle ilgili bir tanılama oluşturması GEREKİR.
Ben Voigt

@BenVoigt Bana farkı öğrettiğin için teşekkürler. Cevapta düzeltildi (diğer cevaplarda C ++ standardından bir alıntıyı kaçırdığım için yaptım).
PSkocik
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.