Cel połączenia w C i C++

Korzystałem już wcześniej, dziś zaniepokoiłem się czytając ten post i dowiedziałem się, że ten kod

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

Jest w rzeczywistości niezdefiniowanym zachowaniem, tzn. czytanie od członka Związku innego niż ten, do którego ostatnio napisano, prowadzi do niezdefiniowanego zachowania. Jeśli nie jest to zamierzone użycie związków, to co jest? Czy ktoś może to wyjaśnić szczegółowo?

Update:

Chciałem wyjaśnić kilka rzeczy w z perspektywy czasu.

  • odpowiedź na to pytanie nie jest taka sama dla C i C++; mój ignorant młodszy ja otagował to jako C i C++.
  • po przejrzeniu standardu C++11 nie mogłem jednoznacznie stwierdzić, że wywołanie dostępu / inspekcji nieaktywnego członka Związku jest niezdefiniowane/nieokreślone / zdefiniowane w implementacji. Znalazłem tylko §9.5 / 1:

    Jeśli związek układu standardowego zawiera kilka struktur układu standardowego, które mają wspólną sekwencję początkową, i jeśli obiekt tego typu standard-layout union zawiera jedną ze struktur standard-layout, dozwolone jest sprawdzanie wspólnej sekwencji początkowej dowolnego elementu struktury standard-layout. §9.2/19: dwie struktury standardowego układu mają wspólną sekwencję początkową, jeśli odpowiadające im pręty mają typy zgodne z układem i albo żaden z prętów nie jest polem bitowym, albo oba są polami bitowymi o tej samej szerokości dla sekwencji jednego lub więcej prętów początkowych.

  • While in C, (C99 TC3-DR 283 ) jest to legalne (dzięki Pascal Cuoq za poruszenie tego tematu). Jednak próba wykonania może nadal prowadzić do niezdefiniowanego zachowania , jeśli odczytana wartość okaże się nieprawidłowa (tzw. "reprezentacja pułapki") dla typu, przez który jest odczytywana. W przeciwnym wypadku odczytana wartość jest zdefiniowana w implementacji.
  • C89/90 wywołał to pod nieokreślonym zachowaniem (Załącznik J), A książka K & R mówi, że jest to implementacja zdefiniowana. Cytat z K & R:

    To jest cel Unii-jednej zmiennej, która może legalnie posiadać jeden z kilku typów. [...] tak długo, jak użycie jest spójne: pobrany typ musi być Ostatnio zapisanym typem. Zadaniem programisty jest śledzenie tego, który typ jest obecnie przechowywany w Unii; wyniki zależą od implementacji, jeśli coś jest przechowywane jako jeden typ i wyodrębniane jako inny.

  • Wyciąg z TC++PL Stroustrupa (podkreślenie moje)

    Użycie związków może być niezbędne dla kompatencji danych [...] czasami błędnie używane dla "type conversion".

przede wszystkim to pytanie (którego tytuł pozostaje niezmieniony od mojego pytania) zostało postawione z zamiarem zrozumienia celu związków, a nie tego, na co standard pozwala np. używanie dziedziczenia do ponownego użycia kodu jest oczywiście dozwolone przez standard C++, ale nie był to cel ani pierwotna intencja standardu C++. wprowadzenie dziedziczenia jako funkcji języka C++ . Z tego powodu odpowiedź Andreya pozostaje nadal akceptowana.

Author: Community, 2010-02-22

14 answers

Cel związków jest dość oczywisty, ale z jakiegoś powodu ludzie często za nim tęsknią.

Celem Unii jest zapisywanie pamięci za pomocą tego samego regionu pamięci do przechowywania różnych obiektów w różnym czasie.To wszystko.

To jest jak pokój w hotelu. Różni ludzie żyją w nim przez nie pokrywające się okresy czasu. Ci ludzie nigdy się nie spotykają i generalnie nic o sobie nie wiedzą. Poprzez odpowiednie zarządzanie dzieleniem czasu pomieszczeń (tj. upewniając się, że różne osoby nie zostaną przydzielone do jednego pokoju w tym samym czasie), stosunkowo mały hotel może zapewnić zakwaterowanie stosunkowo dużej liczbie osób, co jest tym, co hotele są dla.

To właśnie robi Unia. Jeśli wiesz, że kilka obiektów w twoim programie zawiera wartości z nie nakładającymi się na siebie wartościami życia, możesz "scalić" te obiekty w Unię i w ten sposób zapisać pamięć. Tak jak pokój hotelowy ma co najwyżej jednego" aktywnego " najemcę w każdej chwili czas, związek ma co najwyżej jednego "aktywnego" członka w każdym momencie programu. Tylko" aktywny " członek może być odczytany. Pisząc do innego członka zmieniasz status "Aktywny" na tego innego członka.

Z jakiegoś powodu ten pierwotny cel związku stał się" nadrzędny " czymś zupełnie innym: pisaniem jednego członka Związku, a następnie kontrolowaniem go przez innego członka. Ten rodzaj reinterpretacji pamięci (aka "type punning") jest nie jest poprawnym użyciem związków. Ogólnie rzecz biorąc prowadzi do undefined behavior jest określany jako tworzenie zachowań zdefiniowanych w implementacji w C89 / 90.

EDIT: używanie związków do celów typowania (tj. pisanie jednego członka, a następnie czytanie drugiego) zostało podane bardziej szczegółową definicję w jednym z technicznych sprostowań do standardu C99 (zobacz DR#257 i DR#283 ). Należy jednak pamiętać, że formalnie nie chroni to przed działaniem niezdefiniowanego zachowania poprzez próbę odczytania pułapki reprezentacyjne.

 304
Author: AnT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-22 01:31:30

Możesz użyć unions do tworzenia struktur, takich jak poniżej, które zawierają pole, które mówi nam, który składnik Unii jest rzeczywiście używany:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;
 33
Author: Erich Kitzmueller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 11:24:30

Zachowanie jest nieokreślone z punktu widzenia języka. Rozważ, że różne platformy mogą mieć różne ograniczenia w wyrównywaniu pamięci i endianness. Kod w maszynie big endian kontra little endian będzie aktualizował wartości w strukturze w różny sposób. Poprawienie zachowania w języku wymagałoby, aby wszystkie implementacje używały tej samej endianness (i ograniczenia wyrównania pamięci...).

Jeśli używasz C++ (używasz dwóch tagów) i naprawdę Ci zależy jeśli chodzi o przenośność, możesz po prostu użyć struktury i podać setter, który pobiera uint32_t i odpowiednio ustawia pola poprzez operacje na maskach bitowych. To samo można zrobić w C z funkcją.

Edit : spodziewałem się, że programista zapisze odpowiedź do głosowania i zamknie tę. Jak podkreślono w niektórych komentarzach, endianness jest rozpatrywany w innych częściach standardu, pozwalając każdej implementacji zdecydować, co zrobić, a wyrównanie i wyściełanie mogą być również obsługiwane inaczej. Ważnym punktem są tutaj ścisłe zasady aliasingu, do których programator pośrednio się odwołuje. Kompilator może przyjmować założenia dotyczące modyfikacji (lub braku modyfikacji) zmiennych. W przypadku Unii kompilator może zmienić kolejność instrukcji i przenieść odczyt każdego składnika koloru nad zapis do zmiennej kolor.

 33
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 22:46:21

Najbardziej powszechnym użyciem union, z którym regularnie się spotykam, jest aliasing.

Rozważ co następuje:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}
Co to robi? Pozwala na czysty, schludny dostęp do Vector3f vec;'s członków przez albo Nazwa:
vec.x=vec.y=vec.z=1.f ;

Lub poprzez dostęp integer do tablicy

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

W niektórych przypadkach dostęp po imieniu jest najbardziej oczywistą rzeczą, jaką możesz zrobić. W innych przypadkach, szczególnie gdy oś jest wybierana programowo, łatwiej jest uzyskać dostęp do oś według indeksu numerycznego-0 dla x, 1 dla y i 2 dla z.

 12
Author: bobobobo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-11 22:43:16

Jak mówisz, jest to ściśle nieokreślone zachowanie, choć będzie "działać" na wielu platformach. Prawdziwym powodem używania związków jest tworzenie zapisów wariantowych.

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

Oczywiście, potrzebujesz też jakiegoś dyskryminatora, aby powiedzieć, co w rzeczywistości zawiera wariant. Zauważ, że w C++ związki nie są zbyt użyteczne, ponieważ mogą zawierać tylko typy POD-efektywnie te bez konstruktorów i destruktorów.

 9
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 11:22:20

W C to był dobry sposób na zaimplementowanie czegoś w rodzaju wariantu.

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

W czasach pamięci litlle struktura ta zużywa mniej pamięci niż struktura, która ma cały element.

Przy okazji C zapewnia

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

Aby uzyskać dostęp do wartości bitowych.

 7
Author: Totonga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 12:10:35

W C++, wariant Boost zaimplementuj bezpieczną wersję Unii, mającą na celu zapobieganie niezdefiniowanym zachowaniom w jak największym stopniu.

Jego działania są identyczne z enum + union konstruktem (stos też przypisany itp.), ale używa listy szablonów typów zamiast enum :)

 5
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 13:09:57

Chociaż jest to ściśle nieokreślone zachowanie, w praktyce będzie ono działać z prawie każdym kompilatorem. Jest to tak szeroko stosowany paradygmat, że każdy szanujący się kompilator będzie musiał zrobić "właściwą rzecz" w takich przypadkach. Z pewnością jest to preferowane niż typ-punning, który może również generować uszkodzony kod z niektórych kompilatorów.

 4
Author: Paul R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 11:22:40

Technicznie jest niezdefiniowany, ale w rzeczywistości większość(wszystkie?) Kompilatory traktują to dokładnie tak samo jak użycie reinterpret_cast z jednego typu do drugiego, czego wynikiem jest zdefiniowanie implementacji. Nie traciłbym snu z powodu twojego obecnego kodu.

 4
Author: Joe Gauterin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 11:49:28

Dla jeszcze jednego przykładu rzeczywistego wykorzystania związków, struktura CORBA serializuje obiekty za pomocą oznaczonego podejścia Unii. Wszystkie klasy zdefiniowane przez użytkownika są członkami jednej (ogromnej) Unii, a identyfikator integer mówi demarshaller jak interpretować Unię.

 4
Author: Cubbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 19:48:37

Zachowanie może być nieokreślone, ale to po prostu oznacza, że nie ma "standardu". Wszystkie przyzwoite Kompilatory oferują # pragmas do kontrolowania pakowania i wyrównywania, ale mogą mieć różne wartości domyślne. Ustawienia domyślne również ulegną zmianie w zależności od użytych ustawień optymalizacji.

Również związki nie są tylko dla oszczędzania miejsca. Mogą one pomóc nowoczesnym kompilatorom z typowaniem. Jeśli {[0] } wszystko kompilator nie może dokonać założeń co do tego, co robisz. Może trzeba będzie rzucić od tego, co wie o twoim typie i zacznij od nowa (wymuszanie zapisu do pamięci, co jest bardzo nieefektywne w dzisiejszych czasach w porównaniu do prędkości zegara procesora).

 3
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-18 11:28:43

Inni wspominali o różnicach w architekturze (little-big endian).

Przeczytałem problem, że ponieważ pamięć dla zmiennych jest współdzielona, to zapisując do jednej, inne zmieniają się i, w zależności od ich typu, wartość może być bez znaczenia.

Np. Unia{ float f; int i; } x;

Pisanie do x. byłoby bezsensowne, gdybyś potem czytał z x. f-chyba, że to jest to, co zamierzałeś, aby spojrzeć na znak, wykładnik lub mantissa elementy pływaka.

Myślę, że istnieje również problem wyrównania: jeśli niektóre zmienne muszą być wyrównane słowami, możesz nie uzyskać oczekiwanego wyniku.

Np. Unia{ char c[4]; int i; } x;

Jeśli, hipotetycznie, na jakiejś maszynie znak musi być wyrównany słowami, to c [0] i c [1] dzieliłyby się pamięcią masową z i, ale nie z c[2] i c[3].

 3
Author: philcolbourn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-11 11:30:50

W języku C, jak zostało to udokumentowane w 1974 roku, wszystkie elementy struktury dzieliły wspólną przestrzeń nazw, a znaczenie "PTR->member" było zdefiniowane jako dodawanie przemieszczenie członka na " ptr " i uzyskanie dostępu do wynikowego adresu za pomocą typ członka. Ta konstrukcja umożliwiła użycie tego samego PST z członkiem nazwy zaczerpnięte z różnych definicji struktury, ale z tym samym przesunięciem; Programiści wykorzystywali tę zdolność do różnych celów.

Gdy członkowie struktury byli przypisane własne przestrzenie nazw, stało się to niemożliwe deklarowanie dwóch członków struktury o tym samym przemieszczeniu. Dodawanie związków do język umożliwił osiągnięcie tej samej semantyki, która była dostępne we wcześniejszych wersjach języka (choć niemożność nazwy wyeksportowane do kontekstu mogą nadal wymagać użycia find / replace to replace foo - > member into foo - > type1.członek). Co było ważne było nie tyle, że ludzie, którzy dodali związki mają jakiekolwiek szczególne docelowego wykorzystania w umyśle, ale raczej to, że zapewniają one środki, za pomocą których programiści kto oparł się na wcześniejszej semantyce, w jakimkolwiek celu , powinien jeszcze być w stanie osiągnąć tę samą semantykę, nawet jeśli musieli użyć innego składnia, aby to zrobić.

 3
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-21 16:24:32

Możesz używać a unii z dwóch głównych powodów:

  1. poręczny sposób dostępu do tych samych danych na różne sposoby, jak w twoim przykładzie
  2. sposób na zaoszczędzenie miejsca, gdy istnieją różne elementy danych, z których tylko jeden może być "aktywny"

1 jest bardziej jak hack w stylu C do krótkiego pisania kodu na podstawie wiesz, jak działa architektura pamięci systemu docelowego. Jak już wspomniano, można normalnie uciec z nim, jeśli nie rzeczywiście cel wiele różne platformy. Wydaje mi się, że niektóre Kompilatory mogą pozwolić Ci również używać dyrektyw pakujących (wiem, że robią to na strukturach)?

Dobry przykład 2. można znaleźć w wariancie typu szeroko stosowanego w COM.

 2
Author: Mr. Boy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-22 11:46:30