Chcę przechowywać mieszane typy danych w tablicy. Jak można to zrobić?
c
arrays
variant
mixed-type
chanzerre
źródło
źródło
Odpowiedzi:
Elementy tablicy można uczynić unią rozłączną, znaną również jako unia oznaczona .
Element
type
członkowski jest używany do przechowywania wyboru, który element członkowskiunion
ma być używany dla każdego elementu tablicy. Więc jeśli chcesz przechowywaćint
w pierwszym elemencie, zrób:Jeśli chcesz uzyskać dostęp do elementu tablicy, musisz najpierw sprawdzić typ, a następnie użyć odpowiedniego elementu członkowskiego unii.
switch
Stwierdzenie jest przydatne:Do programisty należy upewnienie się, że element
type
członkowski zawsze odpowiada ostatniej wartości przechowywanej w plikuunion
.źródło
Użyj związku:
Będziesz jednak musiał śledzić rodzaj każdego elementu.
źródło
Elementy tablic muszą mieć ten sam rozmiar, dlatego nie jest to możliwe. Możesz obejść ten problem, tworząc typ wariantu :
Wielkość elementu złącza to wielkość największego elementu, 4.
źródło
Istnieje inny styl definiowania unii znaczników (pod jakąkolwiek nazwą), który IMO sprawia, że jest o wiele przyjemniejszy w użyciu , poprzez usunięcie unii wewnętrznej. Jest to styl używany w systemie X Window do rzeczy takich jak zdarzenia.
Przykład w odpowiedzi Barmara nadaje nazwę
val
unii wewnętrznej. Przykład w odpowiedzi Sp. Wykorzystuje anonimową sumę, aby uniknąć konieczności określania za.val.
każdym razem, gdy uzyskujesz dostęp do rekordu wariantu. Niestety „anonimowe” wewnętrzne struktury i związki nie są dostępne w C89 ani C99. Jest to rozszerzenie kompilatora, a zatem z natury nieprzenośne.Lepszym sposobem IMO jest odwrócenie całej definicji. Uczyń każdy typ danych własną strukturą i umieść tag (specyfikator typu) w każdej strukturze.
Następnie zawijasz je w związek najwyższego poziomu.
Teraz może się wydawać, że się powtarzamy i tak jest . Ale weź pod uwagę, że ta definicja prawdopodobnie będzie izolowana do pojedynczego pliku. Ale wyeliminowaliśmy szum związany ze specyfikacją półproduktu,
.val.
zanim dotrzesz do danych.Zamiast tego trafia na koniec, gdzie jest mniej nieprzyjemny. :RE
Inną rzeczą, na którą to pozwala, jest forma dziedziczenia. Edycja: ta część nie jest standardową wersją C, ale używa rozszerzenia GNU.
Odlewanie w górę i w dół.
Edycja: Jedną rzeczą, o której należy pamiętać, jest konstruowanie jednego z nich za pomocą inicjatorów wyznaczonych przez C99. Wszystkie inicjatory członków powinny przechodzić przez tego samego członka unii.
.tag
Inicjator może być ignorowane przez kompilatora, ponieważ.int_
inicjatora, który następuje aliasy tym samym obszarze danych. Nawet jeśli mamy poznać układ (!), A to powinno być OK. Nie, nie jest. Zamiast tego użyj tagu „internal” (nakłada on tag zewnętrzny, tak jak chcemy, ale nie myli kompilatora).źródło
.int_.val
nie aliasuje jednak tego samego obszaru, ponieważ kompilator wie, że.val
znajduje się on na większym przesunięciu niż.tag
. Czy masz link do dalszej dyskusji na temat tego domniemanego problemu?Możesz zrobić
void *
tablicę, z oddzielną tablicąsize_t.
Ale tracisz typ informacji.Jeśli chcesz w jakiś sposób zachować typ informacji, zachowaj trzecią tablicę int (gdzie int jest wartością wyliczoną) Następnie zakoduj funkcję, która rzutuje w zależności od
enum
wartości.źródło
Unia to standardowa droga. Ale masz też inne rozwiązania. Jednym z nich jest tagged pointer , który obejmuje przechowywanie większej ilości informacji w „wolnych” bitach wskaźnika.
W zależności od architektur możesz użyć niskich lub wysokich bitów, ale najbezpieczniejszym i najbardziej przenośnym sposobem jest użycie nieużywanych niskich bitów , korzystając z wyrównanej pamięci. Na przykład w systemach 32-bitowych i 64-bitowych wskaźniki do
int
muszą być wielokrotnościami 4 (zakładając, żeint
jest to typ 32-bitowy), a 2 najmniej znaczące bity muszą mieć wartość 0, dlatego możesz ich użyć do przechowywania typu twoich wartości . Oczywiście przed wyłuskiwaniem wskaźnika należy wyczyścić bity znacznika. Na przykład, jeśli twój typ danych jest ograniczony do 4 różnych typów, możesz go użyć jak poniżejJeśli możesz upewnić się, że dane są wyrównane do 8 bajtów (jak w przypadku wskaźników w systemach 64-bitowych lub
long long
iuint64_t
...), będziesz mieć jeszcze jeden bit na tag.Ma to jedną wadę, że będziesz potrzebować więcej pamięci, jeśli dane nie były przechowywane w zmiennej gdzie indziej. Dlatego w przypadku, gdy typ i zakres danych jest ograniczony, możesz przechowywać wartości bezpośrednio we wskaźniku. Ta technika została użyta w 32-bitowej wersji silnika Chrome V8 , gdzie sprawdza najmniej znaczący bit adresu, aby sprawdzić, czy jest to wskaźnik do innego obiektu (np. Podwójne, duże liczby całkowite, ciąg lub jakiś obiekt), czy też 31 -bitowa wartość ze znakiem (nazywana
smi
- małą liczbą całkowitą ). Jeśli to jestint
, Chrome po prostu wykonuje arytmetyczne przesunięcie w prawo o 1 bit, aby uzyskać wartość, w przeciwnym razie wskaźnik jest wyłuskiwany.W większości obecnych systemów 64-bitowych wirtualna przestrzeń adresowa jest nadal znacznie węższa niż 64 bity, stąd najwyższe najbardziej znaczące bity mogą być również używane jako znaczniki . W zależności od architektury masz różne sposoby używania ich jako tagów. ARM , 68k i wiele innych można skonfigurować tak, aby ignorowały górne bity , co pozwala na swobodne ich używanie bez martwienia się o segfault lub cokolwiek. Z powyższego powiązanego artykułu w Wikipedii:
Na x86_64 nadal możesz ostrożnie używać wysokich bitów jako tagów . Oczywiście nie musisz używać wszystkich tych 16 bitów i możesz pominąć niektóre bity na przyszłość
We wcześniejszych wersjach Mozilla Firefox używali również małych optymalizacji całkowitoliczbowych, takich jak V8, z 3 małymi bitami używanymi do przechowywania typu (int, string, object ... itd.). Ale od czasu JägerMonkey wybrali inną ścieżkę ( nowa reprezentacja wartości JavaScript Mozilli , łącze zapasowe ). Wartość jest teraz zawsze przechowywana w 64-bitowej zmiennej o podwójnej precyzji. Gdy
double
jest znormalizowany , można go użyć bezpośrednio w obliczeniach. Jeśli jednak wszystkie 16 bitów o wyższej wartości to 1s, co oznacza NaN , niższe 32 bity zapiszą adres (w komputerze 32-bitowym) do wartości lub wartości bezpośrednio, pozostałe 16 bitów zostanie użyte do przechowywania typu. Ta technika nazywa się boksem NaNlub nun-boxing. Jest również używany w 64-bitowym JavaScriptCore WebKit i SpiderMonkey Mozilli, a wskaźnik jest przechowywany w niskich 48 bitach. Jeśli głównym typem danych jest zmiennoprzecinkowy, jest to najlepsze rozwiązanie i zapewnia bardzo dobrą wydajność.Przeczytaj więcej o powyższych technikach: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations
źródło