Czy rzutowanie wskaźników za pomocą union to UB?

0
struct X {};

union foo
{
    char* charPtr;
    X* xPtr;
}
foo bar;
bar.charPtr = <whatever>;
X* x = bar.xPtr;

Czy ten kod to UB (mówimy o C++)? Jeśli tak to czy wynika to z odczytu ze zmiennej innej niż ta do której zapisano czy też czegoś innego.

Disclaimer: to nie jest mój kod i na pewno bym nie używał union do tego. Szukam raczej jednoznacznego powodu by odrzucić ten kod przy code review.

0

Nawet przy typach prostych opinia przeważa bardziej za tym, że to jest UB np. tutaj: http://stackoverflow.com/questions/16637074/c-unions-vs-reinterpret-cast
Jeżeli jest reinterpret_cast to właśnie po to, żeby używać go w takich przypadkach aczkolwiek mogę się mylić, bo od C++ trochę już odszedłem.

Niemniej gdyby ktoś mnie zapytał czy to jest UB to powiedziałbym, że tak.

PS: Hehe pamiętam jak Grębosz w Symfonii opisywał taką konwersję jako jedno z zastosowań unii :P

0

Nie będzie tutaj UB, bo char* i X* mają ten sam rozmiar, to trochę tak jakbyś zrobił coś takiego:

#include <stdio.h>

union n
{
  int a;
  int b;
};

union n n;

int main(void)
{
        n.a = 5;
        printf("%d\n", n.b);
        return 0;
}

Argumentem do wywalenia takiego kodu jest taki, że jest to cholernie nieczytelne i unia nie została stworzona w celu rzutowania wskaźników, od tego jest rzutowanie wskaźników. Normalny człowiek w takim miejscu zrobi:

char *a = get_ptr();
X *x = (X *)a;

a nawet pominie durne char *a =... i od razu zrzutuje wynik.

Unia nie została stworzona w celu przechowywania 2 obiektów o takim samym rozmiarze. Precz z tym.

0

@mlyszczek moim zdaniem jednak będzie, bo np. rozmiar typu danych nie musi być identyczny na każdej platformie. A nawet jeżeli będzie identyczny to może być zapisany w różny sposób (nie wiem... endiany etc...) i wtedy taka konwersja to strzał w kolano, "bo tutaj działa, a tam już nie. Hmm... ciekawe dlaczego?"

Poza tym mowa tutaj o C++ gdzie są mechanizmy do takich rzeczy.

0

Nie, char *, X *, cokolwiek *, będzie zawsze miało ten sam rozmiar, zawsze, bo to jest rozmiar wskaźnika. Owszem, rozmiar wskaźnika różni się w zależności od architektury, ale typy wskaźnika względem siebie zawsze będą miały ten sam rozmiar.

0

@twonek dla pointerów generalnie to będzie działać, co innego gdyby tam były rzutowania jakichś faktycznych pól, wtedy już niekoniecznie bo nie wiadomo jaki struktura będzie miała alignment.

4

To jest na 100% UB. W C++ tylko jeden union member może być jednocześnie zainicjalizowany.

Z drugiej strony: na większości architektur i kompilatorów to zadziała zgodnie z oczekiwaniami - tak jak jest zdefiniowane w C99/11.

0

Eeeee, że jak? Mając unie z 2 elementami, jak przypisze coś do elementu pierwszego, to odczytując drugi element dostanę coś innego?!

3

Niekoniecznie. Mając unię z n elementami, jeśli przypiszesz coś do elementu pierwszego to odczyt dowolnego elementu poza pierwszym to UB.

0

Zaintrygowałeś mnie. Jesteś w stanie jakiś przykład zaprezentować? Przecież w unii każdy element wskazuje na ten sam obszar pamięci (z grubsza) i zmiana dowolnego automatycznie pociąga zmianę reszty, bo czytamy z tego samego adresu pamięci, tylko różną długość.

1

Nie jesteś w błędzie. Jednakże mówisz o implementacji, a nie o standardzie i jego wymaganiach. Z perspektywy językowego prawnika nie wolno tak robić i już.

Pewnie kompilator może dzięki temu zastosować jakieś drobne optymalizacje (np. może union{ char foo[7]; int bar; }; nie musi mieć alginmentu inta? (nie wiem, to strzał)), ale ogólnie rzecz biorąc osobiście wolałbym aby C++ również zagwarantował poprawność castów przez unię. Jednak na razie tak nie jest.

Biblia N4606, §9.3 [class.union]/1 napisał(a)

In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended. At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.

0

Dla mnie to jest UB, ale nie z powodu unii ale z powodu tego, że wymuszana jest reinterpretacja danych char[] na X.
Gdyby dane w unii ni były wskaźnikami to jest UB na 100% (jak w przykładzie ze stackoverflow), a skoro są wskaźniki jak w przykładzie to jest OK.
Ale uwaga są wskaźniki, które niejawnie są większego rozmiaru niż wskaźnik na char, wiec zasada nie obejmuje wszystkich możliwych wskaźników.
IMO przykład został zbyt uogólniony lub jest zbyt niejasny, by rozstrzygać UB czy nie UB.

Ogólnie nie widzę sensu kombinowania z unią, wiec to raczej jest ćwiczenie umysłowe, niż realny problem.

3

Przecież standard mówi wyraźnie, że to UB.

0

Ale uwaga są wskaźniki, które niejawnie są większego rozmiaru niż wskaźnik na char, wiec zasada nie obejmuje wszystkich możliwych wskaźników.

Jakiś przykład? I dlaczego niby rzutowanie char[] na na dowolną strukturę miałoby być UB? To tak jakby powiedzieć, że rzutowanie uint8_t * lub void * na wskaźnik na strukturę jest UB.

1 użytkowników online, w tym zalogowanych: 0, gości: 1