Dlaczego to:
#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);
}
drukować 4 8 8
w C i 8 8 8
C ++ (na platformie z 4-bajtowymi liczbami całkowitymi)?
Miałem wrażenie, że UINT64_MAX
przypisanie wymusi na wszystkich stałych wyliczeniowych co najmniej 64 bity, ale en_e_foo
pozostanie na 32 w zwykłym C.
Jakie jest uzasadnienie tej rozbieżności?
Odpowiedzi:
W C
enum
stała jest typuint
. W C ++ jest to typ wyliczeniowy.enum en_e{ en_e_foo, en_e_bar=UINT64_MAX, };
W C jest to naruszenie ograniczenia , wymagające diagnostyki ( jeśli
UINT64_MAX
przekraczaINT_MAX
, co najprawdopodobniej tak się dzieje). Kompilator AC może całkowicie odrzucić program lub może wydrukować ostrzeżenie, a następnie wygenerować plik wykonywalny, którego zachowanie jest nieokreślone. (Nie jest w 100% jasne, że program, który narusza ograniczenie, musi koniecznie mieć nieokreślone zachowanie, ale w tym przypadku standard nie mówi, jakie to zachowanie, więc jest to nadal niezdefiniowane zachowanie.)gcc 6.2 nie ostrzega o tym. brzęk robi. To jest błąd w gcc; niepoprawnie blokuje niektóre komunikaty diagnostyczne, gdy używane są makra ze standardowych nagłówków. Podziękowania dla Grzegorza Szpetkowskiego za zlokalizowanie raportu błędu: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613
W C ++ każdy typ wyliczenia ma typ bazowy , który jest typem całkowitym (niekoniecznie
int
). Ten typ bazowy musi być w stanie reprezentować wszystkie wartości stałe. W tym przypadku oba typyen_e_foo
ien_e_bar
są typuen_e
, który musi mieć co najmniej 64 bity szerokości, nawet jeśliint
jest węższy.źródło
UINT64_MAX
nie przekroczyćINT_MAX
wymagaint
co najmniej 65 bitów.-Wpedantic
a18446744073709551615ULL
, ale nie zUINT64_MAX
.int
musi być typem ze znakiem, więc aby móc reprezentować, musiałby mieć co najmniej 65 bitówUINT64_MAX
(2 ** 64-1).en_e_bar
nie jest większa niż wyliczenie,en_e_foo
jest mniejsza. Zmienna wyliczeniowa była tak duża, jak największa stała.Ten kod po prostu nie jest prawidłowy w C w pierwszej kolejności.
Sekcja 6.7.2.2 w C99 i C11 mówi, że:
Diagnostyka kompilatora jest obowiązkowa, ponieważ stanowi naruszenie ograniczenia, patrz 5.1.1.3:
źródło
W C , podczas gdy a
enum
jest uważany za oddzielny typ, same moduły wyliczające zawsze mają typint
.Zatem zachowanie, które widzisz, jest rozszerzeniem kompilatora.
Powiedziałbym, że rozszerzenie rozmiaru jednego z modułów wyliczających ma sens tylko wtedy, gdy jego wartość jest zbyt duża.
Z drugiej strony w C ++ wszystkie moduły wyliczające mają typ tego, w
enum
jakim zostały zadeklarowane.Z tego powodu rozmiar każdego modułu wyliczającego musi być taki sam. Tak więc rozmiar całości
enum
jest rozszerzany, aby przechowywać największy moduł wyliczający.źródło
Jak wskazywali inni, kod jest źle sformułowany (w C) z powodu naruszenia ograniczeń.
Istnieje błąd GCC # 71613 (zgłoszony w czerwcu 2016 r.), Który stwierdza, że niektóre przydatne ostrzeżenia są wyciszane za pomocą makr.
Obecnym rozwiązaniem może być dołączenie makra
+
operatorem jednoargumentowym :enum en_e { en_e_foo, en_e_bar = +UINT64_MAX, };
co powoduje błąd kompilacji na moim komputerze z GCC 4.9.2:
$ 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 of ‘int’ [-Wpedantic] en_e_bar = +UINT64_MAX
źródło
C11 - 6.7.2.2/2
en_e_bar=UINT64_MAX
jest naruszeniem ograniczenia, co powoduje, że powyższy kod jest nieprawidłowy. Komunikat diagnostyczny powinien zostać wygenerowany poprzez potwierdzenie implementacji zgodnie z projektem C11:Wygląda na to, że GCC ma jakiś błąd i nie udało mu się wygenerować komunikatu diagnostycznego. (Bug jest skierowany w odpowiedzi przez Grzegorza Szpetkowski
źródło
sizeof
jest operatorem czasu kompilacji. Nie ma tutaj UB, a nawet gdyby istniał, nie miałoby to wpływusizeof
.short s = 0xdeadbeef
), A zachowanie jest zdefiniowane przez implementację.Przyjrzałem się standardom i mój program wydaje się naruszać ograniczenia w C z powodu 6.7.2.2p2 :
i zdefiniowany w C ++ z powodu 7.2.5:
źródło