Unia-bezużyteczny anachronizm czy przydatna stara sztuczka?

Ostatnio natknąłem się na świetną książkę o strukturach danych," Data Structures Using C " (c) 1991, w sprzedaży książek w lokalnej bibliotece za tylko 2 $. Jak sugeruje tytuł książki, książka obejmuje struktury danych w języku programowania C.

Dostałem książkę, wiedząc, że będzie przestarzała, ale prawdopodobnie będzie zawierać wiele zaawansowanych tematów C, których nie spotkałbym gdzie indziej.

Na pewno w ciągu 5 minut znalazłem coś, czego nie wiedziałem o C. natknąłem się na sekcja mówiąca o słowie kluczowym union i zdałem sobie sprawę, że nigdy go nie używałem, ani nigdy nie widziałem żadnego kodu, który tak robi. Byłam wdzięczna za nauczenie się czegoś ciekawego i szybko kupiłam książkę.

Dla tych z Was, którzy nie wiedzą, czym jest związek, książka wykorzystuje dobrą metaforę do wyjaśnienia:]}

Aby w pełni zrozumieć pojęcie Unii, konieczne jest zbadanie jej wdrożenie. Struktura może być traktowana jako Mapa Drogowa do obszaru pamięć. Definiuje jak pamięć jest do interpretacji. Unia zapewnia kilka różnych map drogowych dla ten sam obszar pamięci, i jest to odpowiedzialność programisty za określ, która Mapa Drogowa jest aktualna użyj. W praktyce kompilator przydziela wystarczającą ilość miejsca na zawiera największego członka Unia. Jest to jednak Mapa Drogowa, to determinuje sposób przechowywania być interpretowane.

Mogę łatwo wymyślić wymyślone sytuacje lub hacki, w których przydałby się Związek. (Ale nie interesują mnie wymyślone sytuacje czy hacki...)

Czy używałeś lub widziałeś implementację, w której używanie Unii rozwiązało problem * * bardziej elegancko* * niż nie używanie Unii?

Dodano bonus, jeśli dodasz szybkie wyjaśnienie, dlaczego używanie Unii było lepsze/łatwiejsze niż nie używanie Unii.

Author: timrau, 2009-05-13

11 answers

Związki realizują pewien rodzaj polimorfizmu w świecie nie-OOP. Zazwyczaj masz część, która jest wspólna i w zależności od tej części, używasz reszty związków. Dlatego w takich przypadkach, gdy nie masz języka OOP i chcesz uniknąć nadmiernej arytmetyki wskaźników, związki mogą być bardziej eleganckie w niektórych przypadkach.

 24
Author: Lucero,
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
2009-05-13 13:51:08

Jest to przydatne do ustawiania bitów w, powiedzmy, rejestrach zamiast operacji shift/mask:

typedef union {
    unsigned int as_int; // Assume this is 32-bits
    struct {
        unsigned int unused1 : 4;
        unsigned int foo : 4;
        unsigned int bar : 6;
        unsigned int unused2 : 2;
        unsigned int baz : 3;
        unsigned int unused3 : 1;
        unsigned int quux : 12;
    } field;
} some_reg;

Uwaga: sposób pakowania zależy od maszyny.

some_reg reg;
reg.field.foo = 0xA;
reg.field.baz = 0x5;
write_some_register(some_address, reg.as_int);

Może gdzieś tam rozwaliłem składnię, moje C jest zardzewiałe:)

EDIT:

Nawiasem mówiąc, działa to również w odwrotny sposób:

reg.as_int = read_some_register(some_address);
if(reg.field.bar == BAR_ERROR1) { ...
 18
Author: scottfrazer,
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
2009-05-13 16:13:54

Rzeczywiście, jest to świetne narzędzie, gdy piszesz rzeczy takie jak sterowniki urządzeń (a struct, które chcesz wysłać do urządzenia, które może mieć kilka podobnych, ale różnych formatów) i potrzebujesz precyzyjnego układu pamięci...

 10
Author: Mehrdad Afshari,
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
2009-05-13 13:50:01

Powinieneś mieć świadomość, że w C++ nie są one tak dobrym rozwiązaniem, ponieważ tylko typy POD (plain old data) mogą być umieszczone w Unii. Jeśli twoja klasa ma konstruktor, Destruktor, zawiera klasy, które mają konstruktory i / lub destruktory (i około miliona innych gotchów), nie może być członkiem Unii.

 8
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
2009-05-13 14:00:17

Union jest najprostszym sposobem implementacji typów danych podobnych do wariantu w C/C++, jak sądzę.

 6
Author: sharptooth,
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
2009-05-13 13:59:06

Jest często używany w specyfikacji protokołów transmisji danych, gdzie chcesz uniknąć marnowania miejsca w strukturach danych. Pozwala to na zapisanie miejsca w pamięci, używając tej samej przestrzeni dla wielu wzajemnie wykluczających się opcji.

Na przykład:

enum PacketType {Connect, Disconnect};
struct ConnectPacket {};
struct DisconnectPacket {};
struct Packet
{
    // ...
    // various common data
    // ...
    enum PacketType type;
    union
    {
        ConnectPacket connect;
        DisconnectPacket disconnect;
    } payload;
};

Struktury ConnectPacket i DisconnectPacket zajmują tę samą przestrzeń, ale jest to w porządku, ponieważ pakiet nie może być obu typów w tym samym czasie. Wartość enum służy do określenia, która część Unii jest w użyciu. Korzystanie z Unii pozwoliło nam uniknąć powielania wspólnych części struktury pakietów.

 5
Author: markh44,
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
2009-05-13 14:24:53

Jest to całkiem dobry sposób, aby uzyskać wartości bitów IEEE float (zakładając oczywiście, że pływaki są IEEE w Twoim systemie). Wszystko, co wiąże się z rzucaniem float * do int*, ryzykuje przekroczenie surowych zasad aliasingu. To nie jest tylko teoretyczne - wysoki poziom optymalizacji faktycznie złamie kod.

Technicznie rzecz biorąc, Unia nie zajmuje się tym problemem. W praktyce wszystkie znane Kompilatory (a) pozwolą napisać jednego członka Związku i odczytać drugiego, oraz (b) wykonają czytaj po wykonaniu zapisu. GCC przynajmniej jest w stanie przekształcić Unię w rejestr, zamieniając całość w no-op (zakładając, że pływaki są na początku przechowywane w rejestrach).

 4
Author: Steve Jessop,
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
2009-05-13 14:23:50

Rozważ przypadek dostępu do pojedynczych bajtów w dużej zmiennej:

UInt32 x;
x = 0x12345678;
int byte_3 = x & 0x000000FF;          // 0x78
int byte_2 = (x & 0x0000FF00) >> 8;   // 0x56
int byte_1 = (x & 0x00FF0000) >> 16;  // 0x34
int byte_0 = (x & 0xFF000000) >> 24;  // 0x12

To może być znacznie bardziej eleganckie z Unią:

typedef union
{
    UInt32 value;  // 32 bits
    Byte byte[4];  // 4 * 8 bits
}
UInt32_Bytes;

UInt32_Bytes x;
x.value = 0x12345678;
int byte_3 = x.byte[3];  // 0x78
int byte_2 = x.byte[2];  // 0x56
int byte_1 = x.byte[1];  // 0x34
int byte_0 = x.byte[0];  // 0x12

Użycie Unii oznacza, że nie trzeba już używać masek bitowych i operatorów przesunięć, aby uzyskać dostęp do poszczególnych bajtów. Umożliwia również jawny dostęp do bajtów.

 4
Author: e.James,
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
2009-05-13 14:27:41

Użyliśmy związków w wielu kodach do parsowania pakietów sieciowych.

Unia przydziela wielkość największego elementu. Tworzysz unię z elementem bufora o maksymalnym rozmiarze wiadomości, wtedy możesz łatwo uzyskać dostęp do wartości w pakiecie.

Wyobraź sobie, że dane "c123456" dotarły do Internetu i musisz przeanalizować i uzyskać dostęp do wartości:

  #include <iostream>
  using namespace std;

  struct msg
  {
     char header;
     union
     {
       char a[3];
       char b[2];
       char c[5];
       char d[6];
       char buf[10];
     } data;
  } msg;

  int main()
  {
    struct msg m;
    memcpy(&m, "c123456", sizeof("c123456"));

    cout << "m.header: " << m.header << endl;
    cout << "m.data.d: " << string(m.data.d,sizeof(m.data.d)) << endl;
    cout << "m.data.b: " << string(m.data.b,sizeof(m.data.b)) << endl;

    switch (m.header)
    {
     case 'a': cout << "a: " << string(m.data.a, sizeof(m.data.a)) << endl; break;
     case 'b': cout << "b: " << string(m.data.b, sizeof(m.data.b)) << endl; break;
     case 'c': cout << "c: " << string(m.data.c, sizeof(m.data.c)) << endl; break;
     default: break;
    }
  }

Wyjście wyglądałoby tak:

m.header: c
m.data.d: 123456
m.data.b: 12
c: 12345
 2
Author: stefanB,
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
2009-05-13 15:28:43

Wiem, że to się powtórzyło, ale po prostu wrzucę próbkę kodu, aby zobaczyć, jak związki dodają elegancji i wydajności podczas odczytywania ruchu sieciowego:

#pragma packed(1)
struct header_t {
   uint16_t msg_id;
   uint16_t size;
};
struct command_t {
   uint8_t cmd;
};
struct position_t {
   uint32_t x;
   uint32_t y;
   uint32_t z;
};
// ... Rest of the messages in an IDS
struct message {
   header_t header;
   union {
      command_t command;
      position_t position;
   } body;
};
#pragma packed(0)
message read( int socket ) {
   message data;
   unsigned int readed = read( socket, &data, sizeof(header_t) );
   // error checks... readed bytes smaller than header size and such
   readed = read( socket, &(data.body), data.header.size ); 
   // error checks...
}

W powyższym fragmencie możesz wykonać wiadomość odczytaną na miejscu i nie musisz dbać o konkretny typ otrzymanego obiektu. Jeśli nie użyjesz Unii, pozostaniesz z odczytaniem nagłówka, wyodrębnieniem zarówno rozmiaru, jak i typu, utworzeniem instancji obiektu odpowiedniego typu (w hierarchii lub do zawierać wewnątrz typu variant jako boost:: any/boost::variant) i wykonywać drugi Odczyt na nowo utworzonej przestrzeni.

Używamy tego rozwiązania szeroko do sterowania symulatorami (niektóre firmy nie doceniają "nowych" technologii, takich jak DDS czy HLA i nadal polegają na surowych danych UDP/TCP dla swoich symulatorów). W warstwie sieciowej stosujemy związki przekształcane w wewnętrzne struktury danych (konwersja sieci NA host, skalowanie danych...) przed wprowadzeniem go do warstw aplikacji. Jako to było wspomniane wcześniej, musisz być ostrożny z wyściółką przez cały czas.

 2
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
2009-09-02 19:11:10

Użyłem go raz dla szorstkiego rodzaju polimorfizmu danych w podobny sposób do markh44 odpowiedź. Miałem kilka różnych rodzajów danych, które chciałem potencjalnie wykorzystać. Stworzyłem Unię wszystkich tych typów i strukturę, która zawierała Unię i Kod określający, który typ ma być używany.


union
{
    data_type_1;
    data_type_2;
    data_type_3;
} data_union;

typedef struct _TAG_DATA_WRAPPED_
{
    data_union data;
    int data_type; //better an enum
} WRAPPED_DATA;

WRAPPED_DATA loads_of_data[1024];


Aby odpowiedzieć na twoje pytanie, dlaczego jest to korzystne:

Pozwala to na łatwe przydzielanie list lub tablic różnego rodzaju danych i programowo zarządzaj ich typem. Dużym problemem jest oczywiście miejsce do przechowywania, ponieważ jeśli typy mają bardzo różne rozmiary przechowywania, możesz zmarnować dużo miejsca.

 1
Author: Captain Lepton,
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-05-23 11:53:17