Dlaczego potrzebujemy związków C?


236

Kiedy należy stosować związki? Dlaczego ich potrzebujemy?

Odpowiedzi:


252

Związki są często używane do konwersji między reprezentacjami binarnymi liczb całkowitych i liczb zmiennoprzecinkowych:

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

Chociaż jest to technicznie niezdefiniowane zachowanie zgodnie ze standardem C (powinieneś tylko czytać ostatnio napisane pole), będzie działać w dobrze zdefiniowany sposób praktycznie w każdym kompilatorze.

Związki są również czasami używane do implementacji pseudopolimorfizmu w C, poprzez nadanie strukturze jakiegoś znacznika wskazującego, jaki typ obiektu zawiera, a następnie połączenie ze sobą możliwych typów:

enum Type { INTS, FLOATS, DOUBLE };
struct S
{
  Type s_type;
  union
  {
    int s_ints[2];
    float s_floats[2];
    double s_double;
  };
};

void do_something(struct S *s)
{
  switch(s->s_type)
  {
    case INTS:  // do something with s->s_ints
      break;

    case FLOATS:  // do something with s->s_floats
      break;

    case DOUBLE:  // do something with s->s_double
      break;
  }
}

Pozwala to na rozmiar struct Stylko 12 bajtów zamiast 28.


powinno być uy zamiast uf
Amit Singh Tomar

1
Czy działa przykład, który zakłada konwersję liczby zmiennoprzecinkowej na liczbę całkowitą? Nie sądzę, ponieważ int i float są przechowywane w różnych formatach w pamięci. Czy możesz wyjaśnić swój przykład?
spin_eight 10.10.12

3
@spin_eight: To nie jest „konwersja” z float na int. Bardziej jak „reinterpretacja binarnej reprezentacji liczby zmiennoprzecinkowej tak, jakby to była liczba całkowita”. Wynik nie jest równy 3: ideone.com/MKjwon Nie jestem jednak pewien, dlaczego Adam drukuje jako szesnastkowy.
endolith

@Adam Rosenfield, naprawdę nie rozumiałem konwersji, nie otrzymałem liczby całkowitej w wyniku: p
The Beast

2
Uważam, że należy usunąć zastrzeżenie dotyczące nieokreślonego zachowania. W rzeczywistości jest to określone zachowanie. Patrz przypis 82 normy C99: Jeśli element użyty do uzyskania dostępu do zawartości obiektu unii nie jest taki sam, jak element użyty ostatnio do przechowywania wartości w obiekcie, odpowiednia część reprezentacji obiektu wartości zostanie ponownie zinterpretowana jako reprezentacja obiektu w nowym typie, jak opisano w 6.2.6 (proces czasem nazywany „typowaniem punktowym”). Może to być reprezentacja pułapki.
Christian Gibbons,

136

Związki są szczególnie przydatne w programowaniu wbudowanym lub w sytuacjach, w których potrzebny jest bezpośredni dostęp do sprzętu / pamięci. Oto prosty przykład:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

Następnie możesz uzyskać dostęp do rejestru w następujący sposób:

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

Endianness (kolejność bajtów) i architektura procesorów są oczywiście ważne.

Kolejną przydatną funkcją jest modyfikator bitów:

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

Za pomocą tego kodu można uzyskać bezpośredni dostęp do jednego bitu w adresie rejestru / pamięci:

x = reg.bits.b2;

3
Twoja odpowiedź tutaj w połączeniu z powyższą odpowiedzią @Adama Rosenfielda stanowi idealną parę komplementarną: demonstrujesz użycie struktury w związku , a on demonstruje użycie związku w strukturze . Okazuje się, że potrzebuję obu naraz: struktury w ramach unii w strukturze, aby zaimplementować fantazyjny polimorfizm przekazywania wiadomości w C między wątkami w systemie osadzonym, i nie zdałbym sobie sprawy, że nie widziałbym obu twoich odpowiedzi razem .
Gabriel Staples

1
Myliłem się: jest to związek wewnątrz struktury w związku wewnątrz struktury, zagnieżdżony w lewo, aby napisać tak, jak napisałem, od wewnętrznego zagnieżdżania do najbardziej zewnętrznego poziomu. Musiałem dodać kolejny związek na poziomie najbardziej wewnętrznym, aby umożliwić wartości różnych typów danych.
Gabriel Staples

64

Programowanie niskiego poziomu jest rozsądnym przykładem.

IIRC, użyłem związków do rozbicia rejestrów sprzętowych na bity składowe. Możesz więc uzyskać dostęp do 8-bitowego rejestru (jak to było w dniu, w którym to zrobiłem ;-) do bitów składowych.

(Zapomniałem dokładnej składni, ale ...) Ta struktura pozwoliłaby na dostęp do rejestru kontrolnego jako control_byte lub poprzez poszczególne bity. Ważne jest, aby zapewnić odwzorowanie bitów na prawidłowe bity rejestru dla danej endianowości.

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;

3
To doskonały przykład! Oto przykład zastosowania tej techniki we wbudowanym oprogramowaniu: edn.com/design/integrated-circuit-design/4394915/…
rzetterberg

34

Widziałem to w kilku bibliotekach jako zamiennik dziedziczenia obiektowego.

Na przykład

        Connection
     /       |       \
  Network   USB     VirtualConnection

Jeśli chcesz, aby „klasa” połączenia była jedną z powyższych, możesz napisać coś takiego:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

Przykładowe użycie w libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74


33

Związki pozwalają członkom danych, które wykluczają się, na dzielenie tej samej pamięci. Jest to bardzo ważne, gdy pamięci jest mniej, na przykład w systemach wbudowanych.

W poniższym przykładzie:

union {
   int a;
   int b;
   int c;
} myUnion;

Ten związek zajmie miejsce pojedynczej liczby int, a nie 3 oddzielnych wartości int. Jeśli użytkownik ustawi wartość a , a następnie wartość b , nadpisze wartość a, ponieważ obaj dzielą tę samą lokalizację pamięci.


29

Wiele zastosowań. Po prostu zrób grep union /usr/include/*lub w podobnych katalogach. Większość przypadków unionjest zawinięte w a, structa jeden element struktury mówi, do którego elementu w związku należy się dostać. Na przykład kasa man elfdla rzeczywistych wdrożeń.

To jest podstawowa zasada:

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}

Dokładnie tego szukałem! Bardzo przydatne do zastąpienia niektórych parametrów elipsy :)
Nicolas Voron

17

Oto przykład związku z mojej własnej bazy kodu (z pamięci i sparafrazowany, więc może nie być dokładny). Służyło do przechowywania elementów języka w zbudowanym przeze mnie tłumaczu. Na przykład następujący kod:

set a to b times 7.

składa się z następujących elementów językowych:

  • symbol [zestaw]
  • zmienna [a]
  • symbol [do]
  • zmienna [b]
  • symbol [razy]
  • stała [7]
  • symbol[.]

Elementy językowe zostały zdefiniowane jako #definewartości „ ” w ten sposób:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

i do przechowywania każdego elementu użyto następującej struktury:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

następnie rozmiar każdego elementu był wielkością maksymalnej unii (4 bajty dla typu i 4 bajty dla unii, chociaż są to wartości typowe, rzeczywiste rozmiary zależą od implementacji).

Aby utworzyć element „set”, należy użyć:

tElem e;
e.typ = ELEM_SYM_SET;

Aby utworzyć element „zmienna [b]”, należy użyć:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

Aby utworzyć element „stałej [7]”, należy użyć:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

i możesz łatwo go rozszerzyć, aby zawierał floats ( float flt) lub rationals ( struct ratnl {int num; int denom;}) i inne typy.

Podstawowym założeniem jest to, że stri valnie są ciągłe w pamięci, faktycznie się nakładają, więc jest to sposób na uzyskanie innego widoku na tym samym bloku pamięci, zilustrowanym tutaj, gdzie struktura jest oparta na miejscu pamięci, 0x1010a liczby całkowite i wskaźniki są zarówno 4 bajty:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

Gdyby był tylko w strukturze, wyglądałby tak:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+

Czy make sure you free this laterkomentarz powinien zostać usunięty z elementu stałego?
Trevor,

Tak, @Trevor, chociaż nie mogę uwierzyć, że jesteś pierwszą osobą, która widziała to w ciągu ostatnich 4+ lat :-) Naprawiono i dzięki za to.
paxdiablo

7

Powiedziałbym, że ułatwia to ponowne wykorzystanie pamięci, którą można wykorzystać na różne sposoby, np. Oszczędzając pamięć. Np. Chciałbyś zrobić strukturę „wariantową”, która może zapisać krótki ciąg, a także liczbę:

struct variant {
    int type;
    double number;
    char *string;
};

W systemie 32-bitowym spowodowałoby to użycie co najmniej 96 bitów lub 12 bajtów dla każdego wystąpienia variant.

Za pomocą unii możesz zmniejszyć rozmiar do 64 bitów lub 8 bajtów:

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

Możesz zaoszczędzić jeszcze więcej, jeśli chcesz dodać więcej różnych typów zmiennych itp. Może być prawdą, że możesz robić podobne rzeczy, rzucając pustą wskazówkę - ale unia czyni ją znacznie bardziej dostępną, a także pisz bezpieczny. Takie oszczędności nie wydają się ogromne, ale oszczędzasz jedną trzecią pamięci używanej dla wszystkich instancji tej struktury.


5

Trudno wymyślić konkretną okazję, gdy potrzebujesz tego rodzaju elastycznej struktury, być może w protokole wiadomości, w którym wysyłasz wiadomości o różnych rozmiarach, ale nawet wtedy istnieją prawdopodobnie lepsze i bardziej przyjazne dla programistów alternatywy.

Związki są trochę podobne do typów wariantów w innych językach - mogą przechowywać tylko jedną rzecz na raz, ale może to być int, liczba zmiennoprzecinkowa itp., W zależności od tego, jak ją zadeklarujesz.

Na przykład:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion będzie zawierał tylko liczbę całkowitą LUB zmiennoprzecinkową, w zależności od tego, który ostatnio ustawiłeś . Robiąc to:

MYUNION u;
u.MyInt = 10;

u ma teraz liczbę całkowitą równą 10;

u.MyFloat = 1.0;

u masz teraz liczbę zmienną równą 1,0. Nie ma już int. Oczywiście teraz, jeśli spróbujesz zrobić printf („MyInt =% d”, u.MyInt); wtedy prawdopodobnie pojawi się błąd, chociaż nie jestem pewien, jakie zachowanie będzie miało miejsce.

Rozmiar związku zależy od wielkości jego największego pola, w tym przypadku liczby zmiennoprzecinkowej.


1
sizeof(int) == sizeof(float)( == 32) zwykle.
Nick T

1
Dla zapisu, przypisanie do liczby zmiennoprzecinkowej, a następnie wydrukowanie int nie spowoduje błędu, ponieważ ani kompilator, ani środowisko wykonawcze nie wiedzą, która wartość jest poprawna. Int, który zostanie wydrukowany, będzie oczywiście bez znaczenia dla większości celów. Będzie to po prostu reprezentacja pamięci typu float, interpretowana jako int.
Jerry B

4

Związki są używane, gdy chcesz modelować struktury zdefiniowane przez sprzęt, urządzenia lub protokoły sieciowe, lub gdy tworzysz dużą liczbę obiektów i chcesz zaoszczędzić miejsce. Naprawdę nie potrzebujesz ich w 95% przypadków, trzymaj się łatwego do debugowania kodu.


4

Wiele z tych odpowiedzi dotyczy rzutowania z jednego typu na inny. Największą korzyść czerpię ze związków tego samego typu, tylko więcej z nich (tj. Podczas analizowania szeregowego strumienia danych). Umożliwiają parsowanie / konstruowanie pakietu w ramce, co staje się banalne.

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

Edytuj Komentarz na temat endianizmu i wypełniania struktur jest poprawny i ma wielkie obawy. Użyłem tego fragmentu kodu prawie całkowicie we wbudowanym oprogramowaniu, z którego większość kontrolowałem oba końce potoku.


1
Ten kod nie będzie działał (przez większość czasu), jeśli dane są wymieniane na 2 różnych platformach z następujących powodów: 1) Endianowość może być inna. 2) Wypełnienie w konstrukcjach.
Mahori

@Ravi Zgadzam się z obawami dotyczącymi endianizmu i wypełnienia. Należy jednak wiedzieć, że wykorzystałem to wyłącznie w projektach osadzonych. Większość z nich kontrolowałem oba końce rur.
Adam Lewis,

1

Związki są świetne. Jednym sprytnym zastosowaniem związków, które widziałem, jest użycie ich podczas definiowania zdarzenia. Na przykład możesz zdecydować, że zdarzenie ma 32 bity.

Teraz, w obrębie tych 32 bitów, możesz chcieć wyznaczyć pierwsze 8 bitów jako identyfikator nadawcy zdarzenia ... Czasami masz do czynienia z wydarzeniem jako całością, czasem analizujesz je i porównujesz jego składniki. związki dają ci elastyczność do robienia obu.

wydarzenie związku
{
  unsigned long eventCode;
  unsigned char eventParts [4];
};

1

Co z VARIANTtego jest używane w interfejsach COM? Ma dwa pola - „typ” i związek zawierający rzeczywistą wartość, która jest traktowana w zależności od pola „typ”.


1

W szkole używałem takich związków:

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

Użyłem go do łatwiejszej obsługi kolorów, zamiast używać operatorów >> i <<, musiałem tylko przejrzeć inny indeks mojej tablicy znaków.


1

Użyłem union, kiedy kodowałem dla urządzeń osadzonych. Mam C int, który ma 16 bitów. I muszę odzyskać wyższe 8 bitów i niższe 8 bitów, kiedy muszę czytać z / przechowywać w EEPROM. Użyłem więc w ten sposób:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

Nie wymaga zmiany, więc kod jest łatwiejszy do odczytania.

Z drugiej strony widziałem stary kod stl C ++, który używał unii dla alokatora stl. Jeśli jesteś zainteresowany, możesz przeczytać kod źródłowy sgi stl . Oto jego fragment:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

1
Czy nie potrzebujesz grupowania structwokół higher/ lower? W tej chwili oba powinny wskazywać tylko pierwszy bajt.
Mario,

@Mario ah racja, po prostu piszę to ręcznie i zapominam o tym, dzięki
Mu Qiao

1
  • Plik zawierający różne typy rekordów.
  • Interfejs sieciowy zawierający różne typy żądań.

Spójrz na to: Obsługa poleceń bufora X.25

Jedno z wielu możliwych poleceń X.25 jest odbierane do bufora i obsługiwane w miejscu za pomocą UNION wszystkich możliwych struktur.


czy mógłbyś wyjaśnić oba te przykłady. Mam na myśli to, w jaki sposób są one powiązane ze związkiem
Amit Singh Tomar

1

We wczesnych wersjach C wszystkie deklaracje struktury miały wspólny zestaw pól. Dany:

struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};

kompilator zasadniczo stworzyłby tabelę rozmiarów struktur (i ewentualnie wyrównania) oraz osobną tabelę nazw, typów i przesunięć elementów konstrukcji. Kompilator nie śledził, które elementy należały do ​​których struktur, i pozwoliłby, aby dwie struktury miały element o tej samej nazwie tylko wtedy, gdy pasowałby typ i przesunięcie (jak w przypadku qelementów struct xi struct y). Gdyby p był wskaźnikiem do dowolnego typu struktury, p-> q dodałoby przesunięcie „q” do wskaźnika p i pobierałby „int” z wynikowego adresu.

Biorąc pod uwagę powyższą semantykę, możliwe było napisanie funkcji, która mogłaby wykonywać użyteczne operacje na wielu rodzajach konstrukcji zamiennie, pod warunkiem, że wszystkie pola używane przez funkcję były ustawione w jednej linii z użytecznymi polami w danych strukturach. To była przydatna cecha, a zmiana C w celu walidacji elementów używanych do dostępu do struktury względem typów przedmiotowych struktur oznaczałaby utratę jej przy braku środków mających strukturę, która może zawierać wiele nazwanych pól pod tym samym adresem. Dodanie typów „uniowych” do C pomogło nieco wypełnić tę lukę (choć nie IMHO, tak jak powinno być).

Zasadniczą częścią zdolności związków do wypełnienia tej luki był fakt, że wskaźnik do członka związku można przekształcić w wskaźnik do dowolnego związku zawierającego tego członka, a wskaźnik do dowolnego związku można przekształcić w wskaźnik do dowolnego członka. Chociaż standardowa C89 nie wyraźnie powiedzieć, że odlewania T*bezpośrednio do U*stanowiło równowartość odlewania go do wskaźnika do dowolnego typu związków zawierających zarówno Ta U, a następnie rzucając że aby U*nie definiuje zachowanie tej ostatniej sekwencji odlewu mogą być naruszone przez użyty typ unii, a Standard nie określił żadnej przeciwnej semantyki dla bezpośredniego rzutowania z Tna U. Ponadto, w przypadkach, gdy funkcja otrzymała wskaźnik o nieznanym pochodzeniu, zachowanie zapisu obiektu poprzez T*konwersjęT*do a U*, a następnie odczytanie obiektu przez U*byłoby równoznaczne z zapisaniem unii za pomocą elementu typu Ti odczytaniem jako typu U, który zostałby zdefiniowany w kilku przypadkach (np. podczas uzyskiwania dostępu do elementów o wspólnej sekwencji początkowej) i zdefiniowany w implementacji (raczej niż Undefined) dla reszty. Chociaż programy rzadko korzystały z gwarancji CIS z rzeczywistymi obiektami typu unii, o wiele bardziej powszechne było wykorzystywanie faktu, że wskaźniki do obiektów niewiadomego pochodzenia musiały zachowywać się jak wskaźniki dla członków związku i związane z nimi gwarancje behawioralne.


czy możesz podać przykład: „możliwe było napisanie funkcji, która mogłaby wykonywać użyteczne operacje na wielu rodzajach konstrukcji zamiennie”. W jaki sposób można zastosować wiele elementów struktur o tej samej nazwie? Jeśli dwie struktury mają takie samo wyrównanie danych, a zatem element członkowski o tej samej nazwie i takim samym przesunięciu jak w twoim przykładzie, to z jakiej struktury uzyskałbym rzeczywiste dane? (wartość). Dwie struktury mają to samo wyrównanie i te same elementy, ale różne wartości na nich. Czy możesz to rozwinąć
Herdsman

@ Herdsman: We wczesnych wersjach C nazwa członka struktury zawierała typ i przesunięcie. Dwóch członków różnych struktur może mieć tę samą nazwę tylko wtedy, gdy ich typy i przesunięcia są zgodne. Jeśli element struct fooma intprzesunięcie 8, anyPointer->foo = 1234;oznacza to: „weź adres w dowolnym wskaźniku, zastąp go o 8 bajtów i wykonaj zapisywanie liczb całkowitych o wartości 1234 do adresu wynikowego. Kompilator nie będzie musiał wiedzieć ani dbać o to, czy zostanie anyPointerzidentyfikowany każdy typ konstrukcji, który został foowymieniony wśród jego członków
supercat

Za pomocą wskaźnika możesz wyrejestrować dowolny adres bez względu na „początek” wskaźnika, to prawda, ale jaki jest sens kompilatora do przechowywania tabel elementów struktur i ich nazw (jak powiedziałeś w poście), jeśli mogę pobrać dane z jakimkolwiek wskaźnikiem znającym adres członka w określonej strukturze? A jeśli kompilator nie wie, czy anyPointeridentyfikuje się z elementem struktury, to w jaki sposób kompilator sprawdzi te warunki to have a member with the same name only if the type and offset matchedtwojego postu?
Herdsman

@Herdsman: Kompilator przechowuje listę nazw członków struktury, ponieważ dokładne zachowanie p->foozależy od typu i przesunięcia foo. Zasadniczo p->foobył skrótem *(typeOfFoo*)((unsigned char*)p + offsetOfFoo). Jeśli chodzi o twoje ostatnie pytanie, gdy kompilator napotka definicję członka struktury, wymaga, aby albo nie istniał żaden członek o tej nazwie, albo by członek o tej nazwie miał ten sam typ i przesunięcie; Sądzę, że skurczyłyby się, gdyby istniała niepasująca definicja członka struktury, ale nie wiem, jak radziła sobie z błędami.
supercat

0

Prostym i bardzo przydatnym przykładem jest ...

Wyobrażać sobie:

masz uint32_t array[2]i chcesz uzyskać dostęp do trzeciego i czwartego bajtu łańcucha bajtów. można zrobić *((uint16_t*) &array[1]). Ale to niestety łamie surowe zasady aliasingu!

Ale znane kompilatory pozwalają wykonać następujące czynności:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

technicznie jest to nadal naruszenie zasad. ale wszystkie znane standardy obsługują to użycie.

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.