2013-03-27 11 views
7

Przechodzę przez książkę Practical C Programming O'Reilly'ego i przeczytałem książkę K & R w języku programowania C i naprawdę mam problem z uchwyceniem koncepcji związków.Jaki jest sens związków w C?

Przyjmują rozmiar największego typu danych, który je tworzy ... a ostatnio przypisany zastępuje pozostałe ... ale dlaczego nie po prostu użyć/zwolnić pamięć w razie potrzeby?

Książka wspomina, że ​​jest używana w komunikacji, gdzie trzeba ustawić flagi tego samego rozmiaru; i na stronie internetowej, która google może wyeliminować fragmenty pamięci o dziwnych rozmiarach ... ale czy jest to przydatne w nowoczesnej, niewbudowanej przestrzeni pamięci?

Czy jest coś, co można zrobić z nim i rejestry CPU? Czy to po prostu wstrzymanie się z wcześniejszej ery programowania? Czy robi to, podobnie jak niesławne goto, wciąż ma jakieś potężne zastosowanie (być może w ciasnych przestrzeniach pamięci), co sprawia, że ​​warto go zatrzymać?

+1

Spójrz na [Type-paronomazja] (http://en.wikipedia.org/wiki/Type_punning). – Mysticial

+0

Może być użyteczny w sytuacjach, w których trzeba przekonwertować strukturę na jej wartości binarne (np. Char []), bez użycia narzędzi hakerskich. –

+0

możliwy duplikat [C/C++: Kiedy ktoś użyłby związku? Czy to w zasadzie pozostałość po dniach C?] (Http: // stackoverflow.com/questions/4788965/cc-when-would-anyone-use-a-union-is-it-basic-a-remnant-from-the-c-only) –

Odpowiedz

5

Cóż, prawie odpowiedziałeś na pytanie: Pamięć. Pamięć z powrotem w dniach była raczej niska, a nawet użyteczne było zapisanie kilku kilobajtów.

Ale nawet dziś istnieją scenariusze, w których związki mogłyby się przydać. Na przykład, jeśli chcesz zaimplementować typ danych variant. Najlepszym sposobem na to jest użycie związku.

To nie brzmi zbyt wiele, ale załóżmy, że chcesz użyć zmiennej przechowującej 4-znakowy ciąg znaków (jak identyfikator) lub 4-bajtowy numer (który może być haszyszem lub w rzeczywistości tylko numerem) .

Jeśli używasz klasycznego struct, będzie to 8 bajtów długości (przynajmniej, jeśli masz pecha, są również bajty wypełniające). Korzystanie z union to tylko 4 bajty. Więc oszczędzasz 50% pamięci, co nie jest dużo dla jednej instancji, ale wyobraź sobie, że masz ich milion.

Chociaż można osiągnąć podobne rzeczy, rzucając lub podklasując związek, nadal jest to najprostszy sposób.

1

Jedno użycie związków ma dwie zmienne zajmujące tę samą przestrzeń, a druga zmienna w strukturze decyduje o typie danych, które chcesz odczytać.

np. możesz mieć boolean 'isDouble' i union 'doubleOrLong', który ma zarówno podwójne, jak i długie. Jeśli isDouble == true interpretuj związek jako podwójne, interpretuj to jako długie.

Innym zastosowaniem związków jest uzyskiwanie dostępu do typów danych w różnych reprezentacjach. Na przykład, jeśli wiesz, jak układa się podwójnie w pamięci, możesz umieścić podwójne w zjednoczeniu, uzyskać dostęp do niego jako inny typ danych, np. Długi, mieć bezpośredni dostęp do jego bitów, mantysy, znaku, wykładnika, cokolwiek i wykonaj bezpośrednią manipulację z nim.

Tak naprawdę nie potrzebujesz tego w dzisiejszych czasach, ponieważ pamięć jest tak tania, ale w systemach wbudowanych ma swoje zastosowania.

+1

Nawet w nowoczesnych maszynach z tonami gigabajtów pamięci RAM to nie zaszkodzi oszczędzać pamięć w oparciu o twój scenariusz, np podczas zapisywania czegoś na dysku, podczas wysyłania czegoś przez sieć lub po prostu na podstawie liczby zestawów danych. – Mario

+1

@Mario Zgoda. Argument, że pamięć jest tania i nie musisz dbać o rozmiar struktury, zawsze ma dla mnie leniwy wygląd. Powiedzmy, że chcesz przechowywać sto milionów rekordów w pamięci, a rekordy mają 32 różne flagi boolowskie. Czy używasz 32 wartości logicznych lub 32-bitową liczbę całkowitą XOR. Pomijając wypełnienie, różnica wynosi około 3 GB. – Anthony

0

Windows API często używa związków. LARGE_INTEGER jest przykładem takiego użycia. Zasadniczo, jeśli kompilator obsługuje 64-bitowe liczby całkowite, należy użyć elementu QuadPart; w przeciwnym razie ustaw ręcznie niskie DWORD i wysokie DWORD.

0

To nie jest tak naprawdę przesada, ponieważ język C został stworzony w 1972 roku, kiedy pamięć była prawdziwym problemem.

Można wysunąć argument, że w nowoczesnej, nieuziemionej przestrzeni, możesz nie chcieć używać C jako języka programowania na początku. Jeśli wybrałeś C jako swój wybór językowy do implementacji, będziesz chciał wykorzystać zalety C: jest wydajny, bliski metalowi, co skutkuje ciasnymi, szybkimi binariami.

Jako takie, decydując się na użycie C, nadal chciałbyś skorzystać z jego zalet, które obejmują efektywność pamięci. Do czego Unia działa bardzo dobrze; pozwalając na uzyskanie pewnego stopnia bezpieczeństwa typu, jednocześnie wymuszając dostęp do najmniejszej dostępnej stopy pamięci.

+0

Właściwie, mój powód, aby wrócić do przeglądania C, następnie C++, a następnie Templating w C++, następnie CLR, a następnie C++/CLI, ponieważ próbuję stworzyć 64-bitowe (lub wyższe) uaktualnienie indeksu dla .Net List/Dictionary klasy i powiązane klasy/przestrzenie nazw. Mam swędzenie do zera, więc zamierzam "naprawić" to, co tutaj uważam za złamane. – user978122

0

Jednym z miejsc, w którym widziałem używane, jest implementacja Doom 3/idTech 4 Fast Inverse Square Root.

Dla tych, którzy nie znają tego algorytmu, zasadniczo wymaga traktowania liczby zmiennoprzecinkowej jako liczby całkowitej. Stary Quake (i wcześniej) wersja kodu robi to brzmienie:

float y = 2.0f; 

// treat the bits of y as an integer 
long i = * (long *) &y; 

// do some stuff with i 

// treat the bits of i as a float 
y = * (float *) &i; 

original source on GitHub

Kod ten bierze adresu zmiennoprzecinkowej liczby y, rzuca go do wskaźnika do długi (tj. 32-bitowa liczba całkowita w dniach Quake) i zrzeczenie się jej na i. A potem robi niesamowicie dziwaczne, dręczące rzeczy, i na odwrót.

Są dwie wady robienia tego w ten sposób. Jednym z nich jest to, że skomplikowany proces adresowania, odlewania, dereferencji wymusza odczytanie z pamięci wartości y, a nie z rejestru , a także w drodze powrotnej. Jednak na komputerach z Quake'em rejestry zmiennoprzecinkowe i liczby całkowite były całkowicie oddzielne, więc musieliście wcisnąć do pamięci iz powrotem, aby poradzić sobie z tym ograniczeniem.

Po drugie, przynajmniej w C++, wykonywanie takich rzutów jest głęboko nachmurzone, nawet jeśli robi to, co równa się voodoo, takie jak ta funkcja. Jestem pewien, że są bardziej przekonujące argumenty, ale nie jestem pewien, co to jest :)

Tak więc, w Doom 3, id zawierał następujący bit w ich nowej implementacji (która używa innego zestawu bitowego twiddling, ale podobny pomysł):

union _flint { 
     dword     i; 
     float     f; 
}; 

... 
union _flint seed; 
seed.i = /* look up some tables to get this */; 
double r = seed.f; // <- access the bits of seed.i as a floating point number 

original source on GitHub

Teoretycznie, na maszynie SSE2, to można uzyskać za pośrednictwem pojedynczego rejestru; Nie jestem pewien w praktyce, czy jakikolwiek kompilator by to zrobił. To wciąż nieco czystszy kod w mojej opinii niż gry castingowe we wcześniejszej wersji gry Quake.


- pomijając "wystarczająco zaawansowane kompilatora" argumenty