Kiedy ktoś użyłby związku? Czy to pozostałość po czasach „tylko C”?


133

Nauczyłem się, ale tak naprawdę nie mam związków zawodowych. Każdy tekst w C lub C ++, przez który przechodzę, wprowadza je (czasami mimochodem), ale zwykle podają bardzo niewiele praktycznych przykładów, dlaczego i gdzie ich używać. Kiedy związki byłyby przydatne w nowoczesnym (lub nawet starszym) przypadku? Moje jedyne dwa przypuszczenia to programowanie mikroprocesorów, gdy masz bardzo ograniczoną przestrzeń do pracy lub gdy opracowujesz API (lub coś podobnego) i chcesz zmusić użytkownika końcowego do posiadania tylko jednej instancji kilku obiektów / typów w jeden raz. Czy te dwa przypuszczenia są choćby bliskie słuszności?


31
C / C ++ nie jest językiem. Związki są średnio przydatne w C i w większości bezużyteczne w C ++. Prawidłowe byłoby stwierdzenie, że w C ++ są one „pozostałością po C ++ opartym na C”, ale nie można powiedzieć, że są „pozostałością z C ++ tylko dni”, jakby C ++ zastępował C.
R .. GitHub PRZESTAŃ POMÓC W LODZIE

12
Czy możesz wyjaśnić, czym jest substytut unii w języku c ++ lub dlaczego są one bezużyteczne w języku c ++?
Russel,

3
C ++ substytutem unii są klasy i dziedziczenie - związki w C są prawie wyłącznie używane do polimorfizmu bezpiecznego dla typów. Coś w czym zajęcia są o wiele lepsze. (Zobacz odpowiedź vz0 na polimorfizm w stylu C)
tobyodavies

6
@R ..: union są nadal umiarkowanie przydatne w C ++. Zobacz odpowiedzi poniżej.
Michael

2
Związki mogą być niezwykle cenne w wnętrznościach systemu operacyjnego lub np. W pakiecie, który składa / rozkłada pliki dźwiękowe. W takich kontekstach są używane na wiele różnych sposobów - konwersja danych / endian, polimorfizm niskiego poziomu i in. Tak, istnieją inne rozwiązania tego samego problemu (głównie rzutowanie między typami wskaźników), ale połączenia są często czystsze i lepiej dokumentują się.
Hot Licks

Odpowiedzi:


105

Związki są zwykle używane w towarzystwie dyskryminatora: zmiennej wskazującej, które z pól związku jest ważne. Na przykład, powiedzmy, że chcesz utworzyć własny typ wariantu :

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

Wtedy użyłbyś go na przykład:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

W rzeczywistości jest to dość powszechny idiom, szczególnie w przypadku wewnętrznych elementów języka Visual Basic.

Prawdziwy przykład można znaleźć w unii SDL_Event firmy SDL . ( rzeczywisty kod źródłowy tutaj ). Na typegórze unii znajduje się pole, które jest powtarzane w każdej strukturze zdarzenia SDL_ *. Następnie, aby obsłużyć prawidłowe zdarzenie, należy sprawdzić wartość typepola.

Korzyści są proste: istnieje jeden typ danych do obsługi wszystkich typów zdarzeń bez niepotrzebnego wykorzystywania pamięci.


2
Wspaniały! W takim przypadku zastanawiam się teraz, dlaczego funkcja Sdl nie została zaimplementowana tylko jako hierarchia klas. Czy ma to sprawić, że będzie kompatybilny z C, a nie tylko z C ++?
Russel,

12
Klasy @Russel C ++ nie mogą być używane z programu C, ale do struktur / związków C można łatwo uzyskać dostęp z C ++ za pomocą bloku „extern" C ".
vz0

1
Ten wariantowy wzorzec jest również często używany dla interpreterów języków programowania, np. Definicja struct objectw github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield

1
Niesamowite wyjaśnienie. Zawsze wiedziałem, czym są związki zawodowe, ale nigdy nie widziałem prawdziwego powodu, dla którego ktoś byłby na tyle szalony, aby ich używać :) Dzięki za przykład.
riwalk

@ Stargazer712, wyszukiwarka kodów Google: google.com/…
kagali-san

87

Uważam, że związki C ++ są całkiem fajne. Wydaje się, że ludzie zwykle myślą tylko o przypadku użycia, w którym chce się zmienić wartość instancji unii „na miejscu” (co, jak się wydaje, służy jedynie do oszczędzania pamięci lub wykonywania wątpliwych konwersji).

W rzeczywistości związki zawodowe mogą mieć wielką moc jako narzędzie inżynierii oprogramowania, nawet jeśli nigdy nie zmienisz wartości żadnego wystąpienia związku .

Przypadek użycia 1: kameleon

Dzięki uniom można przegrupować wiele dowolnych klas pod jednym nominałem, co nie jest pozbawione podobieństw w przypadku klasy bazowej i jej klas pochodnych. Zmiany jednak dotyczą tego, co można, a czego nie można zrobić z daną instancją unii:

struct Batman;
struct BaseballBat;

union Bat
{
    Batman brucewayne;
    BaseballBat club;
};

ReturnType1 f(void)
{
    BaseballBat bb = {/* */};
    Bat b;
    b.club = bb;
    // do something with b.club
}

ReturnType2 g(Bat& b)
{
    // do something with b, but how do we know what's inside?
}

Bat returnsBat(void);
ReturnType3 h(void)
{
    Bat b = returnsBat();
    // do something with b, but how do we know what's inside?
}

Wydaje się, że programista musi mieć pewność co do typu zawartości danej instancji unii, gdy chce z niej skorzystać. Tak jest w przypadku fpowyższej funkcji . Gdyby jednak funkcja otrzymała instancję unii jako przekazany argument, tak jak w przypadku gpowyższego, nie wiedziałaby, co z nią zrobić. To samo dotyczy funkcji zwracających instancję unii, zobacz h: skąd wywołujący wie, co jest w środku?

Jeśli instancja unii nigdy nie zostanie przekazana jako argument lub jako wartość zwracana, to będzie miała bardzo monotonne życie, ze skokami ekscytacji, gdy programista zdecyduje się zmienić jej zawartość:

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

I to jest najbardziej (nie) popularny przypadek użycia związków. Innym przypadkiem użycia jest sytuacja, gdy instancja union pojawia się wraz z czymś, co mówi ci o jej typie.

Przykład zastosowania 2: „Miło cię poznać, jestem objectz Class

Załóżmy, że programista wybrany tak, aby zawsze łączyć instancję unii w parę z deskryptorem typu (pozostawię czytelnikowi swobodę wyobrażenia sobie implementacji dla jednego takiego obiektu). To niweczy cel samej unii, jeśli programista chce oszczędzać pamięć, a rozmiar deskryptora typu nie jest bez znaczenia w stosunku do rozmiaru unii. Załóżmy jednak, że kluczowe jest, aby instancja union mogła zostać przekazana jako argument lub jako wartość zwracana, gdy wywoływany lub wywołujący nie wie, co jest w środku.

Następnie programista musi napisać switchinstrukcję sterowania przepływem, aby odróżnić Bruce'a Wayne'a od drewnianego patyka lub czegoś równoważnego. Nie jest tak źle, gdy w związku są tylko dwa rodzaje treści, ale oczywiście związek już się nie skaluje.

Przypadek użycia 3:

Jak autorzy rekomendacji dla normy ISO C ++ sformułowali ją w 2008 roku,

Wiele ważnych domen problemowych wymaga dużej liczby obiektów lub ograniczonych zasobów pamięci. W takich sytuacjach zachowanie miejsca jest bardzo ważne, a związek jest często doskonałym sposobem na zrobienie tego. W rzeczywistości typowym przypadkiem użycia jest sytuacja, w której związek nigdy nie zmienia swojego aktywnego członka podczas swojego życia. Może być konstruowana, kopiowana i niszczona tak, jakby była strukturą zawierającą tylko jeden element. Typowym zastosowaniem tego byłoby utworzenie heterogenicznej kolekcji niepowiązanych typów, które nie są alokowane dynamicznie (być może są konstruowane lokalnie na mapie lub składowe tablicy).

A teraz przykład z diagramem klas UML:

wiele kompozycji do klasy A

Sytuacja w prostym języku angielskim: obiekt klasy A może mieć obiekty dowolnej klasy spośród B1, ..., Bn i co najwyżej po jednym każdego typu, gdzie n to całkiem duża liczba, powiedzmy co najmniej 10.

Nie chcemy dodawać pól (członków danych) do A w ten sposób:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

ponieważ n może się różnić (możemy chcieć dodać klasy Bx do miksu) i ponieważ spowodowałoby to bałagan z konstruktorami i ponieważ obiekty A zajmowałyby dużo miejsca.

Moglibyśmy użyć zwariowanego kontenera void*wskaźników do Bxobiektów z rzutami, aby je odzyskać, ale to jest niezłe i takie w stylu C ... ale co ważniejsze, dałoby nam to czas życia wielu dynamicznie przydzielonych obiektów do zarządzania.

Zamiast tego można zrobić tak:

union Bee
{
    B1 b1;
    .
    .
    .
    Bn bn;
};

enum BeesTypes { TYPE_B1, ..., TYPE_BN };

class A
{
private:
    std::unordered_map<int, Bee> data; // C++11, otherwise use std::map

public:
    Bee get(int); // the implementation is obvious: get from the unordered map
};

Następnie, aby pobrać zawartość instancji unii data, należy użyć a.get(TYPE_B2).b2i polubień, gdzie ajest Ainstancja klasy .

Jest to tym bardziej wydajne, że związki są nieograniczone w C ++ 11. Zobacz dokument, do którego link znajduje się powyżej, lub ten artykuł, aby uzyskać szczegółowe informacje.


To było bardzo pomocne, a seria tego drugiego artykułu była bardzo pouczająca. Dzięki.
Andrew,

38

Jednym z przykładów jest dziedzina osadzona, w której każdy bit rejestru może oznaczać coś innego. Na przykład suma 8-bitowej liczby całkowitej i struktury z 8 oddzielnymi 1-bitowymi polami bitowymi umożliwia zmianę jednego bitu lub całego bajtu.


7
Jest to również bardzo powszechne w sterownikach urządzeń. Kilka lat temu napisałem dużo kodu, używając takich związków dla projektu. Zwykle nie jest to zalecane, aw niektórych przypadkach może być specyficzne dla kompilatora, ale działa.
thkala,

11
Nie nazwałbym tego „niezalecane”. W osadzonej przestrzeni jest często znacznie czystszy i mniej podatny na błędy niż alternatywy, które zwykle obejmują wiele wyraźnych rzutów i void*s lub masek i przesunięć.
bta

heh? Wiele wyraźnych obsad? Wydaje mi się proste stwierdzenia, takie jak REG |= MASKi REG &= ~MASK. Jeśli jest to podatne na błędy, umieść je w #define SETBITS(reg, mask)i #define CLRBITS(reg, mask). Nie polegaj na kompilatorze, aby uzyskać bity w określonej kolejności ( stackoverflow.com/questions/1490092/ ... )
Michael

26

Herb Sutter napisał w GOTW około sześć lat temu, z podkreśleniem :

„Ale nie myśl, że związki są tylko pozostałością po wcześniejszych czasach. Związki są prawdopodobnie najbardziej przydatne do oszczędzania miejsca, pozwalając na nakładanie się danych, a jest to nadal pożądane w C ++ i we współczesnym świecie. Na przykład niektóre z najbardziej zaawansowany C ++Standardowe implementacje bibliotek na świecie używają teraz właśnie tej techniki do implementacji „optymalizacji małych ciągów”, świetnej alternatywy optymalizacji, która ponownie wykorzystuje pamięć wewnątrz samego obiektu łańcuchowego: dla dużych łańcuchów przestrzeń wewnątrz obiektu ciągu przechowuje zwykły wskaźnik do dynamicznie przydzielony bufor i informacje porządkowe, takie jak rozmiar bufora; w przypadku małych ciągów ta sama przestrzeń jest zamiast tego ponownie wykorzystywana do bezpośredniego przechowywania zawartości ciągu i całkowitego uniknięcia dynamicznej alokacji pamięci. Aby uzyskać więcej informacji na temat optymalizacji małych ciągów (i innych optymalizacji ciągów i pesymizacji w znacznej głębokości), zobacz… ”.

A dla mniej użytecznego przykładu, zobacz długie, ale niejednoznaczne pytanie gcc, ścisłe aliasing i rzutowanie przez union .


23

Cóż, jeden przykład użycia, który przychodzi mi do głowy, to:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

Następnie można uzyskać dostęp do 8-bitowych oddzielnych części tego 32-bitowego bloku danych; jednak przygotuj się na potencjalne ugryzienie przez endianię.

To tylko jeden hipotetyczny przykład, ale ilekroć chcesz podzielić dane w polu na takie części składowe, możesz użyć unii.

To powiedziawszy, istnieje również metoda, która jest bezpieczna dla endian:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

Na przykład, ponieważ ta operacja binarna zostanie przekonwertowana przez kompilator na poprawny endianness.


Myślę, że najlepiej zadać pytanie, kiedy należy używać związków. Podałeś odpowiedź dotyczącą sytuacji, w których związek zawodowy nie jest właściwym narzędziem, co moim zdaniem powinno być wyjaśnione w tej odpowiedzi.
Michael,

15

Niektóre zastosowania dla związków:

  • Zapewnij interfejs ogólnego endianness dla nieznanego hosta zewnętrznego.
  • Manipuluj zmiennoprzecinkowymi danymi architektury obcego procesora, takimi jak akceptowanie VAX G_FLOATS z łącza sieciowego i konwertowanie ich na długie rzeczywiste IEEE 754 w celu przetworzenia.
  • Zapewnij bezpośredni dostęp do bitów, do typu wyższego poziomu.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Dzięki tej deklaracji można łatwo wyświetlić szesnastkowe wartości bajtów a long double, zmienić znak wykładnika, określić, czy jest to wartość denormalna, lub zaimplementować długą podwójną arytmetykę dla procesora, który jej nie obsługuje itp.

  • Oszczędność miejsca w pamięci, gdy pola są zależne od pewnych wartości:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Grep pliki dołączane do użytku z kompilatorem. Znajdziesz dziesiątki, a nawet setki zastosowań union:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {

5
TSK TSK! Dwa głosy przeciw i bez wyjaśnień. To jest rozczarowujące.
wallyk

Przykład osoby, która może potrzymać mężczyznę i kobietę, jest dla mnie bardzo złym projektem. Dlaczego nie klasy bazowej osoby i mężczyzny i kobiety pochodnej? Przepraszamy, ale ręczne wyszukiwanie zmiennej w celu określenia typu przechowywanego w polu danych jest w ogóle złym pomysłem. To ręcznie wykonany kod C, którego nie widziano od lat. Ale bez głosów przeciw, to tylko mój punkt widzenia :-)
Klaus

4
Myślę, że masz głosy przeciwne dla związku „wykastrowanego” lub „ciążowego”. To jest trochę chore.
akaltar

2
Tak, myślę, że to był ciemny dzień.
wallyk

14

Związki są przydatne w przypadku danych na poziomie bajtów (niskiego poziomu).

Jednym z moich ostatnich zastosowań było modelowanie adresów IP, które wygląda jak poniżej:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;

7
Pamiętaj jednak, że dostęp do takich surowych rzeczy nie jest standardem i może nie działać zgodnie z oczekiwaniami ze wszystkimi kompilatorami.
nr

3
Ponadto bardzo często widzi się to w sposób, który nie gwarantuje wyrównania, co jest niezdefiniowanym zachowaniem.
Mooing Duck

10

Przykład, gdy użyłem unii:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

umożliwia mi to dostęp do moich danych w postaci tablicy lub elementów.

Użyłem związku, aby różne terminy wskazywały na tę samą wartość. Podczas przetwarzania obrazu, niezależnie od tego, czy pracowałem na kolumnach, szerokości czy rozmiarze w kierunku X, może to być mylące. Aby rozwiązać ten problem, używam unii, więc wiem, które opisy pasują do siebie.

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };

12
Zauważ, że chociaż to rozwiązanie działa na większości obserwowalnych platform, ustawienie wartości na _x, _y, _z i dostęp do _coord jest niezdefiniowanym zachowaniem. Głównym celem związków zawodowych jest zachowanie przestrzeni. Musisz uzyskać dostęp do dokładnie tego samego elementu unii, który ustawiłeś poprzednio.
niepokój

1
tak też go używam, chociaż używam std :: array forr coords i niektórych static_asserts
Viktor Sehr

1
Ten kod narusza surowe zasady dotyczące aliasów i nie może być zalecany.
Walter

Czy jest może sposób na ulepszenie związku w taki sposób, aby był to niezawodny?
Andrew,

8

Związki zapewniają polimorfizm w C.


18
Myślałem, że void*zrobiłem to ^^

2
@ user166390 Polimorfizm używa tego samego interfejsu do manipulowania wieloma typami; void * nie ma interfejsu.
Alice,

2
W C polimorfizm jest powszechnie implementowany poprzez nieprzezroczyste typy i / lub wskaźniki funkcji. Nie mam pojęcia, jak i dlaczego miałbyś użyć związku, aby to osiągnąć. Brzmi jak naprawdę zły pomysł.
Lundin

7

Genialnym zastosowaniem unii jest wyrównanie pamięci, które znalazłem w kodzie źródłowym PCL (Point Cloud Library). Pojedyncza struktura danych w API może być ukierunkowana na dwie architektury: procesor z obsługą SSE oraz procesor bez obsługi SSE. Na przykład: struktura danych dla PointXYZ to

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;

3 pływaki są wyściełane dodatkowym pływakiem do wyrównania SSE. Więc dla

PointXYZ point;

Użytkownik może uzyskać dostęp do point.data [0] lub point.x (w zależności od obsługi SSE) w celu uzyskania dostępu, powiedzmy, współrzędnej x. Więcej podobnych, lepszych szczegółów użytkowania znajduje się pod następującym linkiem: Dokumentacja PCL Typy PointT


7

Słowo unionkluczowe, choć nadal używane w C ++ 03 1 , jest w większości pozostałością po czasach C. Najbardziej rażącym problemem jest to, że działa tylko z POD 1 .

Idea unii jest jednak nadal obecna i rzeczywiście biblioteki Boost zawierają klasę podobną do unii:

boost::variant<std::string, Foo, Bar>

Który ma większość zalet union(jeśli nie wszystkie) i dodaje:

  • umiejętność poprawnego używania typów innych niż POD
  • bezpieczeństwo typu statycznego

W praktyce wykazano, że jest to równoważne kombinacji union+ enum, i porównano, że jest tak samo szybkie (chociaż boost::anyjest bardziej prawdopodobne dynamic_cast, ponieważ wykorzystuje RTTI).

1 Unions zostały zaktualizowane w C ++ 11 ( nieograniczone związki ) i mogą teraz zawierać obiekty z destruktorami, chociaż użytkownik musi ręcznie wywołać destruktor (na aktualnie aktywnym składniku unii). Wciąż dużo łatwiej jest używać wariantów.


Nie jest to już prawdą w nowszych wersjach języka c ++. Zobacz na przykład odpowiedź jrsali.
Andrew,

@Andrew: Zaktualizowałem odpowiedź, aby wspomnieć, że C ++ 11 z nieograniczonymi związkami zezwala na przechowywanie typów z destruktorami w unii. Nadal podtrzymuję swoje stanowisko, że naprawdę lepiej jest używać związków oznaczonych, takich jak np. boost::variantPróbować używać ich samodzielnie. Wokół związków zawodowych jest zbyt wiele nieokreślonych zachowań, więc szanse na to, że uda ci się zrobić dobrze, są ogromne.
Matthieu M.,

3

Z artykułu w Wikipedii o związkach :

Podstawową użytecznością związku jest oszczędność miejsca , ponieważ umożliwia przechowywanie wielu różnych typów w tej samej przestrzeni. Związki zapewniają również surowy polimorfizm . Nie ma jednak sprawdzania typów, więc do programisty należy upewnienie się, że dostęp do odpowiednich pól jest dostępny w różnych kontekstach. Odpowiednie pole zmiennej sumującej jest zwykle określane przez stan innych zmiennych, prawdopodobnie w otaczającej strukturze.

Jeden wspólny idiom programowania w C wykorzystuje unions do wykonywania tego, co C ++ nazywa reinterpret_cast, przez przypisanie do jednego pola unii i odczytanie z innego, tak jak ma to miejsce w kodzie, który zależy od surowej reprezentacji wartości.


2

We wczesnych dniach C (np. Jak udokumentowano w 1974 r.), Wszystkie struktury miały wspólną przestrzeń nazw dla swoich członków. Nazwa każdego członka była skojarzona z typem i przesunięciem; jeśli "wd_woozle" było "int" na przesunięciu 12, to mając wskaźnik pdowolnego typu struktury, p->wd_woozlebyłoby równoważne *(int*)(((char*)p)+12). Język wymagał, aby wszyscy członkowie wszystkich typów struktur mieli unikalne nazwy, z wyjątkiem tego, że jawnie zezwalał na ponowne użycie nazw członków w przypadkach, gdy każda struktura, w której zostały użyte, traktowała je jako wspólną sekwencję początkową.

Fakt, że typy struktur można było swobodnie stosować, umożliwił zachowanie struktur tak, jakby zawierały nachodzące na siebie pola. Na przykład podane definicje:

struct float1 { float f0;};
struct byte4  { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */

kod mógłby zadeklarować strukturę typu „float1”, a następnie użyć „składowych” b0 ... b3, aby uzyskać dostęp do poszczególnych bajtów w niej zawartych. Gdy język został zmieniony tak, aby każda struktura otrzymywała oddzielną przestrzeń nazw dla swoich członków, kod, który opierał się na możliwości dostępu do rzeczy na wiele sposobów, uległby awarii. Wartości wydzielenia przestrzeni nazw dla różnych typów struktur były wystarczające, aby wymagać zmiany takiego kodu, aby go dostosować, ale wartość takich technik była wystarczająca, aby uzasadnić rozszerzenie języka, aby nadal go wspierać.

Kod, który został napisany, aby wykorzystać możliwość dostępu do magazynowania w struct float1jakby to była struct byte4może być wykonany do pracy w nowym języku, dodając oświadczenie: union f1b4 { struct float1 ff; struct byte4 bb; };deklarując przedmioty jako typ union f1b4;, a nie struct float1, i zastąpienie dostępy do f0, b0, b1, etc . z ff.f0, bb.b0, bb.b1, itd. Chociaż istnieją lepsze sposoby taki kod mógł być wspierany The unionpodejście było przynajmniej w pewnym stopniu wykonalne, przynajmniej w interpretacji C89-era zasad aliasingu.


1

Powiedzmy, że masz n różnych typów konfiguracji (po prostu będąc zbiorem zmiennych definiujących parametry). Używając wyliczenia typów konfiguracji, można zdefiniować strukturę, która ma identyfikator typu konfiguracji, wraz z sumą wszystkich różnych typów konfiguracji.

W ten sposób, gdziekolwiek przekażesz konfigurację, możesz użyć identyfikatora do określenia sposobu interpretacji danych konfiguracyjnych, ale gdyby konfiguracje były ogromne, nie byłbyś zmuszony do tworzenia równoległych struktur dla każdego potencjalnego typu marnowania przestrzeni.


1

Niedawny impuls do, już podwyższonego znaczenia związków , dał rygorystyczna reguła aliasingu wprowadzona w najnowszej wersji standardu C.

Możesz użyć związków zrobić do pisania na klawiaturze bez naruszania standardu C.
Ten program ma nieokreślone zachowanie (ponieważ założyłem to floati unsigned intma taką samą długość), ale nie ma nieokreślonego zachowania (patrz tutaj ).

#include <stdio.h> 

union float_uint
{
    float f;
    unsigned int ui;
};

int main()
{
    float v = 241;
    union float_uint fui = {.f = v};

    //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR 
    printf("Your IEEE 754 float sir: %08x\n", fui.ui);

    //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
    unsigned int* pp = (unsigned int*) &v;

    printf("Your IEEE 754 float, again, sir: %08x\n", *pp);

    return 0;
}

Reguły dostępu do typów nie występują tylko w „najnowszych” wersjach standardu. Każda wersja C zawiera zasadniczo te same zasady. Zmieniło się to, że kompilatorzy odnosili się do przypisu „Celem tej listy jest określenie okoliczności, w których obiekt może mieć alias lub nie”. jako wskazujące, że reguła nie miała być stosowana w przypadkach, które nie obejmowały aliasingu tak , jak napisano , ale teraz traktują ją jako zaproszenie do przepisania kodu w celu utworzenia aliasingu tam, gdzie go nie było.
supercat

1

Chciałbym dodać jeden dobry praktyczny przykład użycia sumy - implementacja kalkulatora / interpretera formuł lub użycie jakiegoś jej rodzaju w obliczeniach (na przykład chcesz używać modyfikowalnych w czasie wykonywania części twoich formuł obliczeniowych - rozwiązywanie równania numerycznie - po prostu na przykład). Możesz więc chcieć zdefiniować liczby / stałe różnych typów (liczby całkowite, zmiennoprzecinkowe, a nawet zespolone) w następujący sposób:

struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}

Więc oszczędzasz pamięć i co ważniejsze - unikasz wszelkich dynamicznych alokacji dla prawdopodobnie ekstremalnych ilości (jeśli używasz wielu liczb zdefiniowanych w czasie wykonywania) małych obiektów (w porównaniu do implementacji poprzez dziedziczenie klas / polimorfizm). Ale co ciekawsze, nadal możesz korzystać z potęgi polimorfizmu C ++ (na przykład jeśli jesteś fanem podwójnego wysyłania;) z tego typu strukturami. Po prostu dodaj "dummy" wskaźnik interfejsu do klasy nadrzędnej wszystkich typów liczb jako pole tej struktury, wskazując na tę instancję zamiast / oprócz typu surowego, lub użyj starych dobrych wskaźników funkcji C.

struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
 //implement methods assuming Number's union contains double
 NumberBase Add(NumberBase n);
 ...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
 union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
 NumberBase* num_t;
 Set(int a)
 {
 ival=a;
  //still kind of hack, hope it works because derived classes of   Number    dont add any fields
 num_t = static_cast<NumberInt>(this);
 }
}

więc możesz użyć polimorfizmu zamiast sprawdzania typów z przełącznikiem (typ) - z implementacją wydajną pod kątem pamięci (bez dynamicznej alokacji małych obiektów) - oczywiście, jeśli tego potrzebujesz.


Może to być przydatne podczas tworzenia dynamicznego języka. Myślę, że problem, który rozwiąże, polega na zmodyfikowaniu masy zmiennej o nieznanym typie bez wprowadzania tej modyfikacji N razy. Makra są przerażające, a tworzenie szablonów jest praktycznie niemożliwe.
Andrew,

0

Od http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Zastosowania związku są nieliczne i odległe. Na większości komputerów rozmiar wskaźnika i int jest zwykle taki sam - dzieje się tak, ponieważ oba zwykle mieszczą się w rejestrze w procesorze. Więc jeśli chcesz wykonać szybkie i brudne rzutowanie wskaźnika na int lub w inny sposób, zadeklaruj unię.

union intptr {   int i;   int * p; }; 
union intptr x; x.i = 1000; 
/* puts 90 at location 1000 */ 
*(x.p)=90; 

Innym zastosowaniem unii jest polecenie lub protokół komunikatów, w którym wysyłane i odbierane są komunikaty o różnym rozmiarze. Każdy typ wiadomości będzie zawierał inne informacje, ale każdy będzie miał stałą część (prawdopodobnie strukturę) i zmienną część bitową. Oto jak możesz to zaimplementować.

struct head {   int id;   int response;   int size; }; struct msgstring50 {    struct head fixed;    char message[50]; } struct

struct msgstring80 {naprawiono głowicę struct; wiadomość char [80]; }
struct msgint10 {poprawiona głowa struktury; wiadomość int [10]; } struct msgack {struct head fix; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ACK; }

W praktyce, mimo że złącza są tej samej wielkości, sensowne jest wysyłanie tylko znaczących danych, a nie marnowanie miejsca. Msgack ma rozmiar zaledwie 16 bajtów, podczas gdy msgstring80 ma 92 bajty. Więc kiedy zmienna messagetype jest inicjalizowana, jej pole rozmiaru jest ustawione zgodnie z typem. Może to być następnie wykorzystane przez inne funkcje do przesłania prawidłowej liczby bajtów.


0

Związki umożliwiają manipulowanie różnymi rodzajami danych w jednym obszarze pamięci bez osadzania w programie jakichkolwiek informacji niezależnych od maszyny Są analogiczne do wariantów rekordów w pascalu

Jako przykład, taki jak można znaleźć w menedżerze tablicy symboli kompilatora, załóżmy, że stała może być int, float lub wskaźnik znakowy. Wartość określonej stałej musi być przechowywana w zmiennej odpowiedniego typu, jednak do zarządzania tabelą najwygodniej jest, jeśli zajmuje taką samą ilość pamięci i jest przechowywana w tym samym miejscu niezależnie od jej typu. Taki jest cel unii - pojedynczej zmiennej, która może legalnie zawierać jeden z kilku typów. Składnia oparta jest na strukturach:

union u_tag {
     int ival;
     float fval;
     char  *sval;
} u;

Zmienna u będzie wystarczająco duża, aby pomieścić największy z trzech typów; konkretny rozmiar zależy od implementacji. Każdy z tych typów może być przypisany do u, a następnie używany w wyrażeniach, o ile użycie jest spójne

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.