2013-09-02 6 views

Antwort

232

Sie können die Array-Elemente zu einer diskriminierten Verbindung machen, auch bekannt als tagged union.

struct { 
    enum { is_int, is_float, is_char } type; 
    union { 
     int ival; 
     float fval; 
     char cval; 
    } val; 
} my_array[10]; 

Das ist type Element verwendet, um die Wahl zu halten, welches Mitglied der union wird für jedes Array-Element verwendet werden soll. Wenn Sie also eine int im ersten Element speichern möchten, würden Sie tun:

my_array[0].type = is_int; 
my_array[0].val.ival = 3; 

Wenn Sie ein Element des Arrays zugreifen möchten, müssen Sie zunächst die Art überprüfen, verwenden Sie dann das entsprechende Mitglied der Union . Eine switch Anweisung ist nützlich:

switch (my_array[n].type) { 
case is_int: 
    // Do stuff for integer, using my_array[n].ival 
    break; 
case is_float: 
    // Do stuff for float, using my_array[n].fval 
    break; 
case is_char: 
    // Do stuff for char, using my_array[n].cvar 
    break; 
default: 
    // Report an error, this shouldn't happen 
} 

Es ist der Programmierer überlassen, um sicherzustellen, dass das type Mitglied entspricht immer den letzten im union gespeicherten Wert.

+21

+1 Dies ist die Implementierung vieler interpretierender Sprachen, geschrieben in C – texasbruce

+8

@texasbruce, auch "getaggte Union" genannt. Ich benutze diese Technik auch in meiner eigenen Sprache. ;) –

+0

Wikipedia verwendet eine Disambiguierungsseite für "[discriminated union] (http://en.wikipedia.org/wiki/Discriminated_union)" - "disjoint union" in der Mengenlehre und, wie @ H2CO3 erwähnt, "tagged union" in Computerwissenschaften. – Izkata

6

Sie können ein void * Array mit einem getrennten Array von size_t. tun, aber Sie verlieren den Informationstyp.
Wenn Sie den Informationstyp auf irgendeine Weise beibehalten müssen, behalten Sie ein drittes Array von int (wobei der Wert int ein Aufzählungswert ist). Codieren Sie dann die Funktion, die abhängig vom Wert enum codiert.

32

Verwenden Sie eine Vereinigung:

union { 
    int ival; 
    float fval; 
    void *pval; 
} array[10]; 

werden Sie haben den Überblick über die Art jedes Elements zu halten, though.

20

Array-Elemente müssen die gleiche Größe haben, deshalb ist dies nicht möglich. Sie könnten durch die Schaffung eines variant type um es:

#include <stdio.h> 
#define SIZE 3 

typedef enum __VarType { 
    V_INT, 
    V_CHAR, 
    V_FLOAT, 
} VarType; 

typedef struct __Var { 
    VarType type; 
    union { 
    int i; 
    char c; 
    float f; 
    }; 
} Var; 

void var_init_int(Var *v, int i) { 
    v->type = V_INT; 
    v->i = i; 
} 

void var_init_char(Var *v, char c) { 
    v->type = V_CHAR; 
    v->c = c; 
} 

void var_init_float(Var *v, float f) { 
    v->type = V_FLOAT; 
    v->f = f; 
} 

int main(int argc, char **argv) { 

    Var v[SIZE]; 
    int i; 

    var_init_int(&v[0], 10); 
    var_init_char(&v[1], 'C'); 
    var_init_float(&v[2], 3.14); 

    for(i = 0 ; i < SIZE ; i++) { 
    switch(v[i].type) { 
     case V_INT : printf("INT %d\n", v[i].i); break; 
     case V_CHAR : printf("CHAR %c\n", v[i].c); break; 
     case V_FLOAT: printf("FLOAT %f\n", v[i].f); break; 
    } 
    } 

    return 0; 
} 

Die Größe des Elements der Union die Größe des größten Elements ist, 4.

9

Es gibt einen anderen Stil die Tag-Vereinigung der Definition ((von welchem ​​Namen auch immer)) dass IMO es viel besser macht zu verwenden, indem die interne Verbindung entfernt wird. Dies ist der Stil, der im X Window System für Dinge wie Ereignisse verwendet wird.

Das Beispiel in Barmars Antwort gibt der internen Union den Namen val. Das Beispiel in der Antwort von Sp. Verwendet eine anonyme Vereinigung, um zu vermeiden, dass bei jedem Zugriff auf den Variantensatz die .val. angegeben werden muss. Leider sind "anonyme" interne Strukturen und Vereinigungen in C89 oder C99 nicht verfügbar. Es ist eine Compiler-Erweiterung und daher von Natur aus nicht portabel.

Ein besserer Weg IMO ist die gesamte Definition zu invertieren. Machen Sie jeden Datentyp zu seiner eigenen Struktur und fügen Sie das Tag (Typspezifizierer) in jede Struktur ein.

typedef struct { 
    int tag; 
    int val; 
} integer; 

typedef struct { 
    int tag; 
    float val; 
} real; 

Dann wickeln Sie diese in einer Top-Level-Union.

typedef union { 
    int tag; 
    integer int_; 
    real real_; 
} record; 

enum types { INVALID, INT, REAL }; 

Nun mag es erscheinen, dass wir uns selbst sind zu wiederholen, und wir sind.Bedenken Sie jedoch, dass diese Definition wahrscheinlich in einer einzelnen Datei isoliert ist. Aber wir haben das Rauschen eliminiert, indem wir das Intermediate .val. spezifizieren, bevor Sie zu den Daten kommen.

record i; 
i.tag = INT; 
i.int_.val = 12; 

record r; 
r.tag = REAL; 
r.real_.val = 57.0; 

Stattdessen geht es am Ende, wo es weniger anstößig ist. : D

Eine andere Sache, die dies ermöglicht, ist eine Form der Vererbung. Edit: Dieser Teil ist nicht Standard C, sondern verwendet eine GNU-Erweiterung.

if (r.tag == INT) { 
    integer x = r; 
    x.val = 36; 
} else if (r.tag == REAL) { 
    real x = r; 
    x.val = 25.0; 
} 

integer g = { INT, 100 }; 
record rg = g; 

Up-Casting und unten Guss.


Edit: Ein Gotcha bewusst zu sein, wenn Sie eine dieser mit C99 bezeichnet initializers sind zu konstruieren. Alle Mitgliedsinitialisierer sollten über dasselbe Gewerkschaftsmitglied verfügen.

record problem = { .tag = INT, .int_.val = 3 }; 

problem.tag; // may not be initialized 

Die .tag initializer kann durch einen optimierenden Compiler ignoriert werden, da die .int_ initializer die Fläche Aliase die gleichen Daten folgt. Auch wenn wir kennen das Layout (!), Und es sollte in Ordnung sein. Nein, ist es nicht. Verwenden Sie stattdessen das "interne" -Tag (es überlagert das äußere Tag, genau wie wir wollen, aber den Compiler nicht verwirren).

+0

'.int_.val' aliasiert nicht den gleichen Bereich obwohl der Compiler weiß, dass' .val' einen größeren Offset hat als '.tag'. Haben Sie einen Link zu weiteren Diskussionen über dieses angebliche Problem? –

2

Union ist der Standard Weg zu gehen. Aber Sie haben auch andere Lösungen.

Eins ist tagged pointer. Dies hat den Vorteil eines ausgerichteten Speichers, bei dem die niedrigen Adressenbits immer Null sind. Zum Beispiel müssen in 32-Bit-Systemen Zeiger auf int ein Vielfaches von 4 sein und die niedrigen 2 Bits müssen 0 sein, daher können Sie sie verwenden, um den Typ Ihrer Werte zu speichern. Natürlich müssen Sie die Bits vor dem Dereferenzieren der Werte löschen.

Wenn Sie sicherstellen können, dass die Daten 8-Byte ausgerichtet sind, haben Sie ein weiteres Bit für das Tag. Bei den meisten aktuellen 64-Bit-Systemen beträgt die virtuelle Adresse immer noch 48 Bit, daher können die hohen 16 Bit auch als Tags verwendet werden.

Dies hat den Nachteil, dass Sie mehr Speicherplatz benötigen, wenn die Daten nicht irgendwo gespeichert wurden. Falls Art und Umfang Ihrer Daten begrenzt sind, können Sie die Werte direkt im Zeiger speichern. Dies wurde in der V8-Engine von Chrome verwendet, wo das niedrigstwertige Bit der Adresse überprüft wird, um festzustellen, ob es sich um einen Doppelzeiger oder einen 31-Bit-Wert mit Vorzeichen handelt (smi - small integer). Wenn es ein int ist, macht Chrome einfach eine arithmetische Verschiebung um 1 Bit, um den Wert zu erhalten, ansonsten wird der Zeiger dereferenziert.

In früheren Versionen von Mozilla Firefox verwenden sie auch kleine Integer-Optimierungen wie V8, mit den 3 Low-Bits zum Speichern des Typs (int, string, object ... etc.). Aber seit JaegerMonkey haben sie einen anderen Weg genommen (Mozilla’s New JavaScript Value Representation). Der Wert wird jetzt immer in einer 64-Bit-Variablen mit doppelter Genauigkeit gespeichert. Wenn das Double ein normalisiertes ist, kann es direkt in Berechnungen verwendet werden. Wenn jedoch die hohen 16 Bits davon alle 1 sind, was ein NaN bezeichnet, werden die niedrigen 32 Bits die Adresse (in einem 32-Bit Computer) direkt auf den Wert oder den Wert speichern, die verbleibenden 16 Bits werden verwendet um den Typ zu speichern. Diese Technik wird NaN-boxing genannt. Wenn Ihr Hauptdatentyp ein Gleitkomma-Datentyp ist, ist dies die beste Lösung und liefert eine sehr gute Leistung.In 64-Bit-Rechnern kann es auch verwendet werden, da die Adresse oft nur 48 Bit wie oben angegeben ist.

Lesen Sie mehr über die oben genannten Techniken: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations

Verwandte Themen