Nieporozumienie polega na tym, że C jawnie zezwala na przebijanie typów za pomocą unii, podczas gdy C ++ (c ++ 11) nie ma takiego pozwolenia.
c11
6.5.2.3 Struktura i członkowie związku
95) Jeśli element członkowski używany do odczytywania zawartości obiektu unii nie jest tym samym składnikiem, który był ostatnio używany do przechowywania wartości w obiekcie, odpowiednia część reprezentacji obiektu wartości jest ponownie interpretowana jako reprezentacja obiektu w nowym typ zgodnie z opisem w 6.2.6 (proces czasami nazywany „punningiem typu”). To może być reprezentacja pułapki.
Sytuacja z C ++:
c ++ 11
9.5 Związki [class.union]
W unii co najwyżej jeden z niestatycznych elementów członkowskich danych może być aktywny w dowolnym momencie, to znaczy wartość co najwyżej jednego z niestatycznych elementów członkowskich danych może być w dowolnym momencie przechowywana w unii.
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):
3.9 Typy [basic.types]
4 - Reprezentacja obiektowa obiektu typu T to sekwencja N obiektów typu char bez znaku zajmowanych przez obiekt typu T, gdzie N równa się sizeof (T). Reprezentacja wartości obiektu to zestaw bitów przechowujących wartość typu T. W przypadku typów łatwych do skopiowania reprezentacja wartości to zestaw bitów w reprezentacji obiektu, który określa wartość, która jest jednym dyskretnym elementem implementacji. zdefiniowany zbiór wartości. 42
42) Chodzi o to, aby model pamięci w C ++ był zgodny z modelem z języka programowania C ISO / IEC 9899.
Szczególnie interesująco robi się, kiedy czytamy
3.8 Czas życia obiektu [basic.life]
Okres istnienia obiektu typu T rozpoczyna się, gdy: - uzyskany zostanie magazyn z odpowiednim wyrównaniem i rozmiarem dla typu T oraz - jeśli obiekt ma nietrywialną inicjalizację, jego inicjalizacja jest zakończona.
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ć
3.9.2 Typy złożone [podstawowy. Funt]
Jeśli obiekt typu T znajduje się pod adresem A, wskaźnik typu cv T *, którego wartością jest adres A, wskazuje na ten obiekt, niezależnie od tego, w jaki sposób wartość została uzyskana.
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:
4.1 Konwersja Lwartości do rwartości [konw.lwartość]
Wartość gl wartości typu niebędącego funkcją i tablicą T
może zostać przekonwertowana na wartość prvalue. Jeśli T
jest niekompletnym typem, program, który wymaga tej konwersji, jest źle sformułowany. Jeśli obiekt, do którego odwołuje się glvalue, nie jest obiektem typu T
i nie jest obiektem typu pochodnego T
, lub jeśli obiekt nie jest zainicjowany, program, który wymaga tej konwersji, ma niezdefiniowane zachowanie.
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:
- suma jest kopiowana do
char
pamięci tablicowej iz powrotem (3,9: 2) lub
- Unia jest bajtowo kopiowana do innej unii tego samego typu (3.9: 3) lub
- do unii uzyskuje się dostęp ponad granicami języka przez element programu zgodny z normą ISO / IEC 9899 (o ile jest to zdefiniowane) (3.9: 4 przypis 42), a następnie
dostę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.