Miałem wrażenie, że dostęp do union
członka innego niż ostatni zestaw to UB, ale nie mogę znaleźć solidnego odniesienia (poza odpowiedziami twierdzącymi, że to UB, ale bez żadnego wsparcia ze strony standardu).
Czy jest to więc niezdefiniowane zachowanie?
c++
undefined-behavior
language-lawyer
unions
Luchian Grigore
źródło
źródło
Odpowiedzi:
Nieporozumienie polega na tym, że C jawnie zezwala na przebijanie typów za pomocą unii, podczas gdy C ++ (c ++ 11) nie ma takiego pozwolenia.
Sytuacja z C ++:
C ++ później ma język pozwalający na użycie unii zawierających
struct
s ze wspólnymi sekwencjami początkowymi; nie pozwala to jednak na punting typu.Aby określić, czy punning typu union jest dozwolony w C ++, musimy szukać dalej. Odwołaj toc99 jest normatywnym odniesieniem dla C ++ 11 (a C99 ma podobny język do C11, co pozwala na używanie unii):
Szczególnie interesująco robi się, kiedy czytamy
Tak więc dla typu pierwotnego (który ipso facto ma trywialną inicjalizację) zawartego w unii, czas życia obiektu obejmuje co najmniej czas życia samej unii. To pozwala nam wywołać
Zakładając, że interesująca nas operacja to typ punning, czyli przyjmowanie wartości nieaktywnego członka unii i biorąc pod uwagę powyższe, że mamy prawidłowe odniesienie do obiektu, do którego się odwołuje ten element, operacja ta ma wartość lwartość-do -rvalue konwersja:
Powstaje zatem pytanie, czy obiekt, który jest nieaktywnym członkiem unii, jest inicjowany przez pamięć do aktywnego członka unii. O ile wiem, tak nie jest, a więc jeśli:
char
pamięci tablicowej iz powrotem (3,9: 2) lubdostęp do unii przez nieaktywnego członka jest zdefiniowany i jest zdefiniowany tak, aby podążał za reprezentacją obiektu i wartości, dostęp bez jednej z powyższych wstawek jest niezdefiniowanym zachowaniem. Ma to konsekwencje dla optymalizacji dozwolonych dla takiego programu, ponieważ implementacja może oczywiście zakładać, że nie występuje niezdefiniowane zachowanie.
Oznacza to, że chociaż możemy legalnie utworzyć wartość l dla nieaktywnego członka związku (dlatego przypisanie do nieaktywnego członka bez konstrukcji jest w porządku), jest on uważany za niezainicjowany.
źródło
memcpy
implementacji (dostępu do obiektów przy użyciuunsigned char
lvalues), zabronił dostępu do*p
afterint *p = 0; const int *const *pp = &p;
(nawet jeśli niejawna konwersja zint**
naconst int*const*
jest poprawna), zabronił nawet dostępuc
postruct S s; const S &c = s;
. Wydanie CWG 616 . Czy nowe sformułowanie na to pozwala? Jest też [basic.lval].&
oznacza operator jednoargumentowy , gdy stosuje się go do członka związku. Myślę, że wynikowy wskaźnik powinien nadawać się do uzyskania dostępu do elementu członkowskiego przynajmniej do następnego bezpośredniego lub pośredniego użycia dowolnego innego elementu lvalue, ale w gcc wskaźnik nie jest użyteczny nawet tak długo, co rodzi pytanie, co&
operator ma znaczyć.Standard C ++ 11 mówi to w ten sposób
Jeśli przechowywana jest tylko jedna wartość, jak możesz odczytać inną? Po prostu go tam nie ma.
Dokumentacja gcc wymienia to w sekcji Zachowanie zdefiniowane w implementacji
wskazując, że nie jest to wymagane przez standard C.
2016-01-05: Dzięki komentarzom zostałem powiązany z raportem C99 Defect Report # 283, który dodaje podobny tekst jako przypis do standardowego dokumentu C:
Nie jestem jednak pewien, czy wiele wyjaśnia, biorąc pod uwagę, że przypis nie jest normatywny dla normy.
źródło
Myślę, że najbliższy standardowi, który mówi, że jego niezdefiniowane zachowanie jest zdefiniowane, jest zachowanie związku zawierającego wspólną sekwencję początkową (C99, §6.5.2.3 / 5):
C ++ 11 daje podobne wymagania / uprawnienia w §9.2 / 19:
Chociaż żadne z nich nie stwierdza tego bezpośrednio, oba mają mocną implikację, że „sprawdzanie” (czytanie) członka jest „dozwolone” tylko wtedy, gdy 1) jest (częścią) ostatnio napisanym członkiem lub 2) jest częścią wspólnego inicjału sekwencja.
Nie jest to bezpośrednie stwierdzenie, że robienie czegoś innego jest niezdefiniowanym zachowaniem, ale jest to najbliższe, z czego jestem świadomy.
źródło
union
jako niezdefiniowanych, ponieważ pewien blog dał mi wrażenie, że to jest w porządku i zbudowałem wokół tego kilka dużych struktur i projektów. Teraz myślę , że mimo wszystko mogę być w porządku, ponieważ mojeunion
s zawierają klasy mające te same typy z przoduunion
zawierało np. Auint8_t
i aclass Something { uint8_t myByte; [...] };
- przypuszczam, że to zastrzeżenie będzie miało również zastosowanie tutaj, ale jest sformułowane bardzo celowo, aby pozwolić tylko nastruct
s. Na szczęście używam już tych zamiast surowych prymitywów: OCoś, o czym jeszcze nie wspomniano w dostępnych odpowiedziach, to przypis 37 w paragrafie 21 sekcji 6.2.5:
Ten wymóg wydaje się jasno oznaczać, że nie wolno pisać w jednym członku, a czytać w innym. W tym przypadku może to być niezdefiniowane zachowanie z powodu braku specyfikacji.
źródło
Dobrze to wyjaśnię na przykładzie.
załóżmy, że mamy następujący związek:
union A{ int x; short y[2]; };
Dobrze zakładam, że
sizeof(int)
daje to 4, a tosizeof(short)
daje 2.Kiedy
union A a = {10}
tak dobrze napiszesz , utwórz nową zmienną typu A i umieść w niej wartość 10.Twoja pamięć powinna wyglądać tak: (pamiętaj, że wszyscy członkowie związku mają to samo miejsce)
jak widać, wartość ax to 10, wartość ay 1 to 10, a wartość ay [0] to 0.
teraz, co się stanie, jeśli to zrobię?
a.y[0] = 37;
nasza pamięć będzie wyglądać tak:
spowoduje to zmianę wartości ax na 2424842 (dziesiętnie).
teraz, jeśli twój związek ma liczbę zmiennoprzecinkową lub podwójną, twoja mapa pamięci będzie raczej bałaganem, ze względu na sposób, w jaki przechowujesz dokładne liczby. więcej informacji można znaleźć tutaj .
źródło