Wie können gemischte Datentypen (int, float, char usw.) in einem Array gespeichert werden?

Lesezeit: 9 Minuten

Benutzeravatar von chanzerre
chanzerre

Ich möchte gemischte Datentypen in einem Array speichern. Wie konnte man das tun?

  • Es ist möglich und es gibt Anwendungsfälle, aber dies ist wahrscheinlich ein fehlerhaftes Design. Dafür sind Arrays nicht da.

    – Djechlin

    18. Oktober 2013 um 15:20 Uhr

Benutzeravatar von Barmar
Barmar

Sie können die Array-Elemente zu einer diskriminierten Vereinigung machen, auch bekannt als Gewerkschaft markiert.

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

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

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

Wenn Sie auf ein Element des Arrays zugreifen möchten, müssen Sie zuerst den Typ überprüfen und dann das entsprechende Element der Union verwenden. EIN switch Aussage ist hilfreich:

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 bleibt dem Programmierer überlassen, dafür zu sorgen, dass die type member entspricht immer dem letzten in der gespeicherten Wert union.

  • +1 Dies ist die Implementierung vieler in C geschriebener Dolmetschersprachen

    – SwiftMango

    2. September 2013 um 16:42 Uhr

  • @texasbruce wird auch als “tagged union” bezeichnet. Ich verwende diese Technik auch in meiner eigenen Sprache. 😉

    Benutzer529758

    2. September 2013 um 16:50 Uhr

  • Wikipedia verwendet eine Begriffsklärungsseite für „diskriminierte Gewerkschaft” – “disjoint union” in der Mengenlehre und, wie @H2CO3 erwähnt, “tagged union” in der Informatik.

    – Iskata

    2. September 2013 um 17:38 Uhr

  • Und die erste Zeile der Wikipedia Stichwort Gewerkschaft Seite sagt: In der Informatik ist eine getaggte Vereinigung, auch Variante, Variantendatensatz, diskriminierte Vereinigung, disjunkte Vereinigung oder Summentyp genannt, … Es wurde so oft neu erfunden, dass es viele Namen hat (ähnlich wie Wörterbücher, Hashes, assoziative Arrays usw.).

    – Barmar

    2. September 2013 um 17:40 Uhr


  • @Barmar Ich habe es als “tagged union” umgeschrieben, aber dann deinen Kommentar gelesen. Ich habe die Bearbeitung rückgängig gemacht, ich wollte Ihre Antwort nicht zerstören.

    Benutzer529758

    2. September 2013 um 17:44 Uhr

Verwenden Sie eine Gewerkschaft:

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

Sie müssen jedoch den Typ jedes Elements im Auge behalten.

Array-Elemente müssen dieselbe Größe haben, deshalb ist dies nicht möglich. Sie könnten dies umgehen, indem Sie eine erstellen Variantentyp:

#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 Vereinigung ist die Größe des größten Elements, 4.

Benutzeravatar von luser droog
Luser Drog

Es gibt eine andere Art, die Tag-Union (mit welchem ​​​​Namen auch immer) zu definieren, die es meiner Meinung nach viel schöner macht verwenden, indem die interne Vereinigung entfernt wird. Dies ist der Stil, der im X Window System für Dinge wie Ereignisse verwendet wird.

Das Beispiel in Barmars Antwort gibt den Namen an val zum inneren Verband. Das Beispiel in der Antwort von Sp. verwendet eine anonyme Union, um zu vermeiden, dass angegeben werden muss .val. jedes Mal, wenn Sie auf den Variantendatensatz zugreifen. 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 portierbar.

Eine bessere Möglichkeit besteht meiner Meinung nach darin, die gesamte Definition umzukehren. Erstellen Sie für jeden Datentyp eine eigene Struktur und fügen Sie das Tag (Typbezeichner) in jede Struktur ein.

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

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

Dann packen Sie diese in eine Top-Level-Union.

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

enum types { INVALID, INT, REAL };

Nun mag es so aussehen, als würden wir uns wiederholen, und wir sind. Beachten Sie jedoch, dass diese Definition wahrscheinlich auf eine einzelne Datei beschränkt ist. Aber wir haben das Geräusch der Angabe des Zwischenprodukts eliminiert .val. 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 ans Ende, wo es weniger anstößig ist. 😀

Eine andere Sache, die dies ermöglicht, ist eine Form der Vererbung. Bearbeiten: Dieser Teil ist kein 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;

Upcasting und Downcasting.


Bearbeiten: Ein Problem, das Sie beachten sollten, ist, wenn Sie eines davon mit C99-designierten Initialisierern erstellen. Alle Member-Initialisierer sollten über dasselbe Union-Member erfolgen.

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

problem.tag; // may not be initialized

Das .tag initializer kann von einem optimierenden Compiler ignoriert werden, da die .int_ Initialisierer, der folgt Pseudonyme gleichen Datenbereich. Wenngleich wir kennen das Layout (!), und es sollte okay sein. Nein, ist es nicht. Verwenden Sie stattdessen das “internal”-Tag (es überlagert das äußere Tag, so wie wir es wollen, aber verwirrt den Compiler nicht).

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

not_a_problem.tag; // == INT

Benutzeravatar von dzada
dzada

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

  • Sie können die Typinformationen auch im Zeiger selbst speichern

    – phuklv

    20. Juli 2018 um 16:55 Uhr

Benutzeravatar der Community
Gemeinschaft

Union ist der Standardweg. Aber Sie haben auch andere Lösungen. Einer davon ist markierter Zeigerbei dem weitere Informationen in der gespeichert werden “frei” Bits eines Zeigers.

Je nach Architektur können Sie die niedrigen oder hohen Bits verwenden, aber der sicherste und portabelste Weg ist die Verwendung von unbenutzte niedrige Bits indem Sie den Vorteil des ausgerichteten Speichers nutzen. Beispielsweise in 32-Bit- und 64-Bit-Systemen, Hinweise auf int muss ein Vielfaches von 4 sein (vorausgesetzt int ist ein 32-Bit-Typ) und die 2 niederwertigsten Bits müssen 0 sein, daher können Sie sie verwenden, um den Typ Ihrer Werte zu speichern. Natürlich müssen Sie die Tag-Bits löschen, bevor Sie den Zeiger dereferenzieren. Wenn Ihr Datentyp beispielsweise auf 4 verschiedene Typen beschränkt ist, können Sie ihn wie unten verwenden

void* tp; // tagged pointer
enum { is_int, is_double, is_char_p, is_char } type;
// ...
uintptr_t addr = (uintptr_t)tp & ~0x03; // clear the 2 low bits in the pointer
switch ((uintptr_t)tp & 0x03)           // check the tag (2 low bits) for the type
{
case is_int:    // data is int
    printf("%d\n", *((int*)addr));
    break;
case is_double: // data is double
    printf("%f\n", *((double*)addr));
    break;
case is_char_p: // data is char*
    printf("%s\n", (char*)addr);
    break;
case is_char:   // data is char
    printf("%c\n", *((char*)addr));
    break;
}

Wenn Sie sicherstellen können, dass die Daten 8-Byte-ausgerichtet sind (wie bei Zeigern in 64-Bit-Systemen oder long long und uint64_t…), haben Sie ein weiteres Bit für das Tag.

Dies hat den Nachteil, dass Sie mehr Speicher benötigen, wenn die Daten nicht woanders in einer Variablen gespeichert wurden. Falls also Art und Umfang Ihrer Daten eingeschränkt sind, können Sie die Werte direkt im Pointer speichern. Diese Technik wurde in der 32-Bit-Version von verwendet Der V8-Motor von Chromewo es das niedrigstwertige Bit der Adresse überprüft, um zu sehen, ob es sich um a handelt Zeiger auf ein anderes Objekt (wie Double, Big Integer, String oder irgendein Objekt) oder a 31-Bit-Wert mit Vorzeichen (genannt smi – kleine ganze Zahl). Wenn es ein intChrome führt einfach eine arithmetische Rechtsverschiebung um 1 Bit durch, um den Wert zu erhalten, andernfalls wird der Zeiger dereferenziert.


Auf den meisten aktuellen 64-Bit-Systemen ist der virtuelle Adressraum immer noch viel schmaler als 64 Bit, daher die High Most Significant Bits können auch als Tags verwendet werden. Je nach Architektur haben Sie verschiedene Möglichkeiten, diese als Tags zu verwenden. ARM, 68k und viele andere können konfiguriert werden Ignorieren Sie die oberen Bits, sodass Sie sie frei verwenden können, ohne sich Gedanken über Segfault oder ähnliches machen zu müssen. Aus dem oben verlinkten Wikipedia-Artikel:

Ein bedeutendes Beispiel für die Verwendung von getaggten Zeigern ist die Objective-C-Laufzeit auf iOS 7 auf ARM64, die insbesondere auf dem iPhone 5S verwendet wird. In iOS 7 sind virtuelle Adressen 33 Bit (Byte-ausgerichtet), sodass wortausgerichtete Adressen nur 30 Bit verwenden (die 3 niederwertigsten Bits sind 0), sodass 34 Bit für Tags übrig bleiben. Objective-C-Klassenzeiger sind wortausgerichtet, und die Tag-Felder werden für viele Zwecke verwendet, z. B. zum Speichern einer Referenzzählung und ob das Objekt einen Destruktor hat.

Frühere Versionen von MacOS verwendeten getaggte Adressen namens Handles, um Verweise auf Datenobjekte zu speichern. Die hohen Bits der Adresse zeigten an, ob das Datenobjekt gesperrt war, gelöscht werden konnte und/oder aus einer Ressourcendatei stammte. Dies führte zu Kompatibilitätsproblemen, als die MacOS-Adressierung in System 7 von 24 Bit auf 32 Bit erweitert wurde.

https://en.wikipedia.org/wiki/Tagged_pointer#Examples

Auf x86_64 können Sie die hohen Bits immer noch mit Vorsicht als Tags verwenden. Natürlich müssen Sie nicht alle diese 16 Bits verwenden und können einige Bits für die Zukunftssicherheit weglassen

In früheren Versionen von Mozilla Firefox verwenden sie auch kleine ganzzahlige Optimierungen wie V8, mit dem 3 niedrige Bits zum Speichern des Typs (int, string, objekt… usw.). Aber seit JägerMonkey haben sie einen anderen Weg eingeschlagen (Mozillas neue JavaScript-Wertdarstellung, Backup-Link). Der Wert wird jetzt immer in einer 64-Bit-Variablen mit doppelter Genauigkeit gespeichert. Wenn der double ist ein normalisiert one, es kann direkt in Berechnungen verwendet werden. Wenn jedoch die hohen 16 Bits davon alle 1s sind, was ein bedeutet NaN, die niedrigen 32-Bits speichern die Adresse (in einem 32-Bit-Computer) für den Wert oder den Wert direkt, die restlichen 16-Bits werden zum Speichern des Typs verwendet. Diese Technik heißt NaN-Boxen oder Nonnenboxen. Es wird auch in 64-Bit-WebKits JavaScriptCore und Mozillas SpiderMonkey verwendet, wobei der Zeiger in den niedrigen 48 Bits gespeichert wird. Wenn Ihr Hauptdatentyp Fließkomma ist, ist dies die beste Lösung und liefert eine sehr gute Leistung.

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

  • Sie können die Typinformationen auch im Zeiger selbst speichern

    – phuklv

    20. Juli 2018 um 16:55 Uhr

1424290cookie-checkWie können gemischte Datentypen (int, float, char usw.) in einem Array gespeichert werden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy