Odpowiedzi:
W związku musisz użyć tylko jednego z elementów, ponieważ wszystkie są przechowywane w tym samym miejscu. Jest to przydatne, gdy chcesz przechowywać coś, co może być jednym z kilku typów. Z drugiej strony, struct ma osobne miejsce w pamięci dla każdego z jego elementów i można z nich korzystać jednocześnie.
Aby podać konkretny przykład ich zastosowania, jakiś czas temu pracowałem nad interpretatorem schematu i zasadniczo nakładałem typy danych schematu na typy danych C. Polegało to na przechowywaniu w strukturze wyliczenia wskazującego typ wartości i unii do przechowywania tej wartości.
union foo {
int a; // can't use both a and b at once
char b;
} foo;
struct bar {
int a; // can use both a and b simultaneously
char b;
} bar;
union foo x;
x.a = 3; // OK
x.b = 'c'; // NO! this affects the value of x.a!
struct bar y;
y.a = 3; // OK
y.b = 'c'; // OK
edycja: Jeśli zastanawiasz się, jakie ustawienie xb na „c” zmienia wartość xa na, technicznie rzecz biorąc jest niezdefiniowana. Na większości nowoczesnych maszyn char ma 1 bajt, a int 4 bajty, więc podanie xb wartości „c” daje również pierwszemu bajtowi xa tę samą wartość:
union foo x;
x.a = 3;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);
odciski
99, 99
Dlaczego dwie wartości są takie same? Ponieważ wszystkie 3 ostatnie bajty liczby całkowitej 3 są równe zero, więc jest również czytane jako 99. Jeśli wstawimy większą liczbę dla xa, zobaczysz, że nie zawsze tak jest:
union foo x;
x.a = 387439;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);
odciski
387427, 99
Aby przyjrzeć się bliżej faktycznym wartościom pamięci, ustawmy i wydrukujmy wartości szesnastkowe:
union foo x;
x.a = 0xDEADBEEF;
x.b = 0x22;
printf("%x, %x\n", x.a, x.b);
odciski
deadbe22, 22
Możesz wyraźnie zobaczyć, gdzie 0x22 nadpisał 0xEF.
ALE
W C kolejność bajtów w int nie jest zdefiniowana. Ten program nadpisał 0xEF wartością 0x22 na moim komputerze Mac, ale istnieją inne platformy, na których zastąpiłby 0xDE, ponieważ kolejność bajtów tworzących liczbę całkowitą została odwrócona. Dlatego pisząc program, nigdy nie powinieneś polegać na zachowaniu nadpisywania określonych danych w związku, ponieważ nie jest ono przenośne.
Aby uzyskać więcej informacji na temat porządkowania bajtów, sprawdź endianness .
Oto krótka odpowiedź: struct jest strukturą rekordową: każdy element w strukturze przydziela nową przestrzeń. Więc taka struktura
struct foobarbazquux_t {
int foo;
long bar;
double baz;
long double quux;
}
przydziela co najmniej (sizeof(int)+sizeof(long)+sizeof(double)+sizeof(long double))
bajty w pamięci dla każdej instancji. („Przynajmniej”, ponieważ ograniczenia wyrównania architektury mogą zmusić kompilator do wypełnienia struktury).
Z drugiej strony,
union foobarbazquux_u {
int foo;
long bar;
double baz;
long double quux;
}
przydziela jeden fragment pamięci i daje mu cztery aliasy. Tak więc sizeof(union foobarbazquux_u) ≥ max((sizeof(int),sizeof(long),sizeof(double),sizeof(long double))
znowu z możliwością pewnego dodania do wyrównania.
Czy jest jakiś dobry przykład na różnicę między „strukturą” a „związkiem”?
Urojony protokół komunikacyjny
struct packetheader {
int sourceaddress;
int destaddress;
int messagetype;
union request {
char fourcc[4];
int requestnumber;
};
};
W tym wyimaginowanym protokole określono, że w oparciu o „typ wiadomości” następująca lokalizacja w nagłówku będzie albo numerem żądania, albo czteroznakowym kodem, ale nie jednym i drugim. Krótko mówiąc, związki pozwalają, aby ta sama lokalizacja pamięci reprezentowała więcej niż jeden typ danych, przy czym gwarantuje się, że będziesz chciał przechowywać tylko jeden typ danych w danym momencie.
Związki są w dużej mierze szczegółami niskiego poziomu, opartymi na dziedzictwie C jako systemowego języka programowania, w którym czasami wykorzystuje się „nakładające się” miejsca przechowywania. Czasami możesz użyć związków, aby zaoszczędzić pamięć tam, gdzie masz strukturę danych, w której tylko jeden z kilku typów zostanie zapisany jednocześnie.
Ogólnie rzecz biorąc, system operacyjny nie dba o struktury i związki i nie wie o nich - są to po prostu bloki pamięci. Struktura to blok pamięci, który przechowuje kilka obiektów danych, przy czym obiekty te nie nakładają się. Unia to blok pamięci, który przechowuje kilka obiektów danych, ale ma tylko pamięć dla największego z nich, a zatem może przechowywać tylko jeden obiekt danych w tym samym czasie.
packetheader ph;
jak uzyskać dostęp do numeru żądania? ph.request.requestnumber
?
Jak już wspomniałeś w swoim pytaniu, główna różnica między union
i struct
polega na tym, że union
członkowie nakładają się na siebie nawzajem, tak że rozmiar związku jest tym samym, podczas gdy struct
członkowie są układani jeden po drugim (z opcjonalnym wypełnieniem pomiędzy nimi). Również związek jest wystarczająco duży, aby pomieścić wszystkich jego członków, i mieć wyrównanie, które pasuje do wszystkich jego członków. Powiedzmy, że int
może być przechowywany tylko pod 2-bajtowymi adresami i ma szerokość 2 bajtów, a długi może być przechowywany tylko pod 4-bajtowymi adresami i ma 4 bajty długości. Następujący związek
union test {
int a;
long b;
};
może mieć wartość sizeof
4, a wymaganie wyrównania wynosi 4. Zarówno związek, jak i struktura mogą mieć wypełnienie na końcu, ale nie na początku. Zapis do struktury zmienia tylko wartość członka zapisanego do. Zapis do członka związku spowoduje utratę wartości wszystkich pozostałych członków. Nie możesz uzyskać do nich dostępu, jeśli wcześniej do nich nie napisałeś, w przeciwnym razie zachowanie jest niezdefiniowane. GCC zapewnia jako rozszerzenie, które możesz odczytać od członków związku, nawet jeśli nie napisałeś do nich ostatnio. W przypadku systemu operacyjnego nie musi mieć znaczenia, czy program użytkownika zapisuje do unii, czy do struktury. To właściwie tylko kwestia kompilatora.
Inną ważną właściwością union i struct jest to, że pozwalają one, aby wskaźnik do nich wskazywał typy dowolnego z jego członków . Dlatego obowiązują następujące zasady:
struct test {
int a;
double b;
} * some_test_pointer;
some_test_pointer może wskazywać na int*
lub double*
. Jeśli rzucisz adres typu test
na int*
, będzie on wskazywał na jego pierwszego członka a
. To samo dotyczy związku. Tak więc, ponieważ związek zawsze będzie miał prawidłowe wyrównanie, możesz użyć związku, aby wskazać jakiś typ:
union a {
int a;
double b;
};
Ten związek faktycznie będzie w stanie wskazać int i podwójne:
union a * v = (union a*)some_int_pointer;
*some_int_pointer = 5;
v->a = 10;
return *some_int_pointer;
jest faktycznie ważny, jak stwierdzono w standardzie C99:
Dostęp do przechowywanej wartości obiektu może mieć tylko wyrażenie wartości, które ma jeden z następujących typów:
- typ zgodny ze skutecznym typem obiektu
- ...
- typ agregatu lub związku, który obejmuje jeden z wyżej wymienionych typów wśród jego członków
Kompilator nie zoptymalizuje, v->a = 10;
ponieważ może to wpłynąć na wartość *some_int_pointer
(a funkcja zwróci 10
zamiast 5
).
union
Jest przydatna w kilka scenariuszy.
union
może być narzędziem do manipulacji na bardzo niskim poziomie, takim jak pisanie sterowników urządzeń dla jądra.
Przykładem tego jest rozcięcie float
liczby za pomocą union
pola struct
bitowego i pola a float
. I zapisać numer w float
, a później mogę uzyskać dostęp do poszczególnych części float
przez to struct
. Przykład pokazuje, w jaki sposób union
używane są różne kąty patrzenia na dane.
#include <stdio.h>
union foo {
struct float_guts {
unsigned int fraction : 23;
unsigned int exponent : 8;
unsigned int sign : 1;
} fg;
float f;
};
void print_float(float f) {
union foo ff;
ff.f = f;
printf("%f: %d 0x%X 0x%X\n", f, ff.fg.sign, ff.fg.exponent, ff.fg.fraction);
}
int main(){
print_float(0.15625);
return 0;
}
Spójrz na pojedynczy opis precyzji na wikipedii. Użyłem stamtąd przykładu i magicznej liczby 0.15625.
union
może także służyć do implementacji algebraicznego typu danych, który ma wiele alternatyw. Znalazłem przykład tego w książce O'Sullivana, Stewarta i Goerzena „Real World Haskell”. Sprawdź to w sekcji Związki dyskryminowane .
Twoje zdrowie!
„ union ” i „ struct ” są konstrukcjami języka C. Mówienie o różnicy między nimi na poziomie systemu operacyjnego jest niewłaściwe, ponieważ to kompilator wytwarza inny kod, jeśli użyjesz jednego lub drugiego słowa kluczowego.
Mówiąc inaczej niż technicznie oznacza:
Założenie: krzesło = blok pamięci, ludzie = zmienna
Struktura : Jeśli są 3 osoby, mogą one odpowiednio siedzieć na krześle swojej wielkości.
Unia : Jeśli są 3 osoby, będzie tylko jedno krzesło, aby usiąść, wszyscy muszą korzystać z tego samego krzesła, kiedy chcą usiąść.
Technicznie rzecz biorąc oznacza:
Poniższy program daje głębokie zanurzenie w strukturze i jedności razem.
struct MAIN_STRUCT
{
UINT64 bufferaddr;
union {
UINT32 data;
struct INNER_STRUCT{
UINT16 length;
UINT8 cso;
UINT8 cmd;
} flags;
} data1;
};
Całkowity rozmiar MAIN_STRUCT = sizeof (UINT64) dla bufferaddr + sizeof (UNIT32) dla unii + 32 bity dla padding (zależy od architektury procesora) = 128 bitów. Dla struktury wszyscy członkowie otrzymują blok pamięci w sposób ciągły.
Unia pobiera jeden blok pamięci elementu o maksymalnym rozmiarze (tutaj jego 32-bitowy). Wewnątrz unii leży jeszcze jedna struktura (INNER_STRUCT), jej członkowie otrzymują blok pamięci o całkowitej wielkości 32 bitów (16 + 8 + 8). W unii można uzyskać dostęp do elementu INNER_STRUCT (32 bity) lub danych (32 bity).
Tak, główna różnica między strukturą a związkiem jest taka sama, jak powiedziałeś. Struct wykorzystuje całą pamięć swojego członka, a związek używa największej przestrzeni pamięci członków.
Ale cała różnica polega na zużyciu pamięci. Najlepsze wykorzystanie unii można zaobserwować w procesach unixowych, w których wykorzystujemy sygnały. jak proces może działać tylko na jeden sygnał na raz. Tak więc ogólna deklaracja będzie:
union SIGSELECT
{
SIGNAL_1 signal1;
SIGNAL_2 signal2;
.....
};
W takim przypadku proces wykorzystuje tylko najwyższą pamięć ze wszystkich sygnałów. ale jeśli użyjesz struktury w tym przypadku, użycie pamięci będzie sumą wszystkich sygnałów. To robi dużą różnicę.
Podsumowując, Unia powinna zostać wybrana, jeśli wiesz, że masz dostęp do któregokolwiek z członków jednocześnie.
Masz, to wszystko. Ale w zasadzie jaki jest sens związków?
Możesz umieścić w tej samej lokalizacji różne typy. Musisz znać rodzaj tego, co przechowujesz w związku (tak często umieszczasz go w struct
tagu typu ...).
Dlaczego to jest ważne? Niezupełnie dla korzyści kosmicznych. Tak, możesz zyskać trochę bitów lub padding, ale to już nie jest główny punkt.
Jest to związane z bezpieczeństwem typu, umożliwia wykonywanie pewnego rodzaju „dynamicznego pisania”: kompilator wie, że treść może mieć różne znaczenia i dokładne znaczenie interpretacji w czasie wykonywania. Jeśli masz wskaźnik, który może wskazywać na różne typy, MUSISZ użyć unii, w przeciwnym razie kod może być niepoprawny z powodu problemów z aliasingiem (kompilator mówi do siebie „och, tylko ten wskaźnik może wskazywać na ten typ, więc mogę zoptymalizować z tych dostępów ... ”i mogą się zdarzyć złe rzeczy).
Struktura przydziela całkowity rozmiar wszystkich zawartych w niej elementów.
Związek przydziela tylko tyle pamięci, ile potrzebuje jego największy członek.
Jaka jest różnica między strukturą a zjednoczeniem?
Skrótowa odpowiedź brzmi: szacunek dotyczy alokacji pamięci. Objaśnienie: W strukturze przestrzeń pamięci zostanie utworzona dla wszystkich elementów wewnątrz struktury. W unii pamięć zostanie utworzona tylko dla członka, który potrzebuje największej przestrzeni pamięci. Rozważ następujący kod:
struct s_tag
{
int a;
long int b;
} x;
union u_tag
{
int a;
long int b;
} y;
Oto dwóch członków wewnątrz struct i union: int i long int. Miejsce w pamięci dla int to: 4 bajty, a miejsce w pamięci dla long int wynosi: 8 w 32-bitowym systemie operacyjnym.
Więc dla struct 4 + 8 = 12 bajtów zostanie utworzonych, podczas gdy 8 bajtów zostanie utworzonych dla unii
Przykład kodu:
#include<stdio.h>
struct s_tag
{
int a;
long int b;
} x;
union u_tag
{
int a;
long int b;
} y;
int main()
{
printf("Memory allocation for structure = %d", sizeof(x));
printf("\nMemory allocation for union = %d", sizeof(y));
return 0;
}
Patrz: http://www.codingpractise.com/home/c-programming/structure-and-union/
Zastosowania związków zawodowych są często używane, gdy potrzebne są specjalistyczne rozmowy typu. Aby dowiedzieć się o przydatności związku. Standardowa biblioteka c / c nie definiuje żadnej funkcji zaprojektowanej specjalnie do zapisywania krótkich liczb całkowitych w pliku. Użycie fwrite () powoduje nadmierne obciążenie dla prostej operacji. Jednak za pomocą unii można łatwo utworzyć funkcję, która zapisuje binarną krótką liczbę całkowitą do pliku po jednym bajcie. Zakładam, że krótkie liczby całkowite mają długość 2 bajtów
PRZYKŁAD:
#include<stdio.h>
union pw {
short int i;
char ch[2];
};
int putw(short int num, FILE *fp);
int main (void)
{
FILE *fp;
fp fopen("test.tmp", "wb ");
putw(1000, fp); /* write the value 1000 as an integer*/
fclose(fp);
return 0;
}
int putw(short int num, FILE *fp)
{
pw word;
word.i = num;
putc(word.c[0] , fp);
return putc(word.c[1] , fp);
}
chociaż putw () wywołałem z krótką liczbą całkowitą, można było użyć putc () i fwrite (). Chciałem jednak pokazać przykład, który zdominuje sposób użycia związku
Struktura to zbiór różnych typów danych, w których mogą znajdować się różne typy danych i każdy otrzymuje własny blok pamięci
zwykle używaliśmy unii, gdy jesteśmy pewni, że tylko jedna zmienna zostanie użyta jednocześnie i chcesz w pełni wykorzystać obecną pamięć, ponieważ otrzymuje ona tylko jeden blok pamięci, który jest równy największemu typowi.
struct emp
{
char x;//1 byte
float y; //4 byte
} e;
całkowita pamięć to => 5 bajtów
union emp
{
char x;//1 byte
float y; //4 byte
} e;
całkowita pamięć to = 4 bajty
Związki przydają się podczas pisania funkcji porządkowania bajtów, która jest podana poniżej. Nie jest to możliwe w przypadku struktur.
int main(int argc, char **argv) {
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102;
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
} else
printf("sizeof(short) = %d\n", sizeof(short));
exit(0);
}
// Program from Unix Network Programming Vol. 1 by Stevens.