Union versus Void-Zeiger

Lesezeit: 7 Minuten

Benutzer-Avatar
Benutzer105033

Was wären die Unterschiede zwischen der Verwendung einer einfachen Leerstelle* im Gegensatz zu einer Vereinigung? Beispiel:

struct my_struct {
    short datatype;
    void *data;
}

struct my_struct {
    short datatype;
    union {
        char* c;
        int* i;
        long* l;
    };
};

Beide können verwendet werden, um genau dasselbe zu erreichen, ist es jedoch besser, die Vereinigung oder die Leere * zu verwenden?

  • Verzeihen Sie das Klischee, aber die Größe zählt 🙂

    – Tim Post

    30. November 2009 um 18:35 Uhr

  • @tinkertim und die Chancen stehen gut, dass sie die gleiche Größe haben. Auf einem 32-Bit-Intel-System benötigen beide 4 Bytes. technisch könnte void * größer sein, wird es aber fast nie sein (es sei denn, double * ist größer …)

    – Mikeage

    30. November 2009 um 18:50 Uhr

  • Haben nicht alle Zeiger die gleiche Größe, unabhängig von der Art der Daten, auf die sie zeigen?

    – Mathieu Page

    1. März 2016 um 12:33 Uhr

Benutzer-Avatar
Patrick Schlüter

Ich hatte genau diesen Fall in unserer Bibliothek. Wir hatten ein generisches String-Mapping-Modul, das verschiedene Größen für den Index verwenden konnte, 8, 16 oder 32 Bit (aus historischen Gründen). Der Code war also voll mit Code wie diesem:

if(map->idxSiz == 1) 
   return ((BYTE *)map->idx)[Pos] = ...whatever
else
   if(map->idxSiz == 2) 
     return ((WORD *)map->idx)[Pos] = ...whatever
   else
     return ((LONG *)map->idx)[Pos] = ...whatever

Es gab 100 solcher Zeilen. Als ersten Schritt habe ich es in eine Union geändert und fand es lesbarer.

switch(map->idxSiz) {
  case 1: return map->idx.u8[Pos] = ...whatever
  case 2: return map->idx.u16[Pos] = ...whatever
  case 3: return map->idx.u32[Pos] = ...whatever
}

Dadurch konnte ich klarer sehen, was vor sich ging. Ich könnte dann entscheiden, die vollständig zu entfernen idxSiz Varianten, die nur 32-Bit-Indizes verwenden. Dies war jedoch erst möglich, als der Code besser lesbar wurde.

PS: Das war nur ein kleiner Teil unseres Projekts, das aus mehreren 100’000 Codezeilen besteht, die von Leuten geschrieben wurden, die es nicht mehr gibt. Die Änderungen am Code müssen schrittweise erfolgen, um die Anwendungen nicht zu beschädigen.

Fazit: Auch wenn die Leute weniger an die Union-Variante gewöhnt sind, bevorzuge ich sie, weil sie den Code viel leichter lesbar machen kann. Bei großen Projekten ist die Lesbarkeit extrem wichtig, auch wenn später nur Sie selbst den Code lesen werden.

Bearbeiten: Kommentar hinzugefügt, da Kommentare keinen Code formatieren:

Die Änderung zum Switch kam vorher (das ist jetzt der echte Code wie er war)

switch(this->IdxSiz) { 
  case 2: ((uint16_t*)this->iSort)[Pos-1] = (uint16_t)this->header.nUz; break; 
  case 4: ((uint32_t*)this->iSort)[Pos-1] = this->header.nUz; break; 
}

geändert wurde

switch(this->IdxSiz) { 
  case 2: this->iSort.u16[Pos-1] = this->header.nUz; break; 
  case 4: this->iSort.u32[Pos-1] = this->header.nUz; break; 
}

Ich hätte nicht alle Verschönerungen, die ich im Code vorgenommen habe, kombinieren und nur diesen Schritt zeigen sollen. Aber ich habe meine Antwort von zu Hause aus gepostet, wo ich keinen Zugriff auf den Code hatte.

  • Ich denke, die Lesbarkeit kommt von der Verwendung eines Schalters anstelle von verschachtelten ifs.

    – schw

    1. Dezember 2009 um 5:58 Uhr

  • Die Änderung zu switch kam vorher (das ist jetzt der echte Code wie er war) switch(this->IdxSiz) { case 2: ((uint16_t*)this->iSort)[Pos-1] = (uint16_t)dies->header.nUz; Unterbrechung; Fall 4: ((uint32_t*)this->iSort)[Pos-1] = this->header.nUz; Unterbrechung; } wurde geändert in switch(this->IdxSiz) { Fall 2: this->iSort.u16[Pos-1] = this->header.nUz; Unterbrechung; Fall 4: this->iSort.u32[Pos-1] = this->header.nUz; Unterbrechung; } Ich hätte nicht alle Verschönerungen, die ich im Code vorgenommen habe, kombinieren und nur diesen Schritt zeigen sollen. Aber ich habe meine Antwort von zu Hause aus gepostet, wo ich keinen Zugriff auf den Code hatte.

    – Patrick Schlüter

    1. Dezember 2009 um 11:16 Uhr

Benutzer-Avatar
schw

Meiner Meinung nach ist der void-Zeiger und das explizite Casting der bessere Weg, weil es für jeden erfahrenen C-Programmierer offensichtlich ist, was die Absicht ist.

Edit zur Verdeutlichung: Wenn ich die besagte Vereinigung in einem Programm sehe, würde ich mich fragen, ob der Autor die Arten der gespeicherten Daten einschränken wollte. Möglicherweise werden einige Plausibilitätsprüfungen durchgeführt, die nur bei ganzzahligen Typen sinnvoll sind. Aber wenn ich einen void-Zeiger sehe, weiß ich direkt, dass der Autor die Datenstruktur entworfen hat, um beliebige Daten zu speichern. Somit kann ich es auch für neu eingeführte Strukturtypen verwenden. Beachten Sie, dass ich den Originalcode möglicherweise nicht ändern kann, z. B. wenn er Teil einer Bibliothek eines Drittanbieters ist.

  • Absicht und Verwendung sind zwei verschiedene Dinge, wenn es um den Speicherverbrauch geht. Eine Gewerkschaft ist so groß wie ihr größtes Mitglied.

    – Tim Post

    30. November 2009 um 18:30 Uhr

  • aber in diesem Fall sind alle Mitglieder in Union Zeiger. Also sollte es die gleiche Größe wie eine Leere haben * richtig?

    – FatDaemon

    30. November 2009 um 18:38 Uhr

  • Nichts in der Spezifikation erfordert, dass Zeiger dieselbe Größe haben, aber void * muss groß genug sein, um jeden anderen Zeiger zu verarbeiten. In der Praxis haben Zeiger jedoch im Allgemeinen die gleiche Größe

    – Mikeage

    30. November 2009 um 18:51 Uhr

  • Nur neugierig, wie ist die Absicht in der Union-Version weniger klar?

    – Dan Olson

    30. November 2009 um 20:08 Uhr

  • Würden Sie nicht sagen, dass diese Verwendung auch die Absicht von Gewerkschaften umfasst?

    – Dan Olson

    4. Dezember 2009 um 1:25 Uhr

Es ist üblicher, eine Vereinigung zu verwenden, um tatsächliche Objekte statt Zeiger zu halten.

Ich denke, die meisten C-Entwickler, die ich respektiere, würden sich nicht die Mühe machen, verschiedene Zeiger miteinander zu vereinen; Wenn ein Allzweckzeiger benötigt wird, verwenden Sie einfach void * ist sicherlich “der C-Weg”. Die Sprache opfert viel Sicherheit, um es Ihnen zu ermöglichen, die Arten von Dingen absichtlich mit Aliasnamen zu versehen; In Anbetracht dessen, was wir für diese Funktion bezahlt haben, können wir sie genauso gut verwenden, wenn sie den Code vereinfacht. Deshalb gab es schon immer Auswege aus der strikten Typisierung.

Benutzer-Avatar
Jerry Sarg

Das union Ansatz erfordert, dass Sie wissen a priori alle Arten, die verwendet werden könnten. Das void * Ansatz ermöglicht das Speichern von Datentypen, die möglicherweise noch nicht einmal vorhanden sind, wenn der betreffende Code geschrieben wird (obwohl viel mit ein solcher unbekannter Datentyp kann schwierig sein, z. B. wenn ein Zeiger auf eine Funktion übergeben werden muss, die für diese Daten aufgerufen werden soll, anstatt sie direkt verarbeiten zu können).

Bearbeiten: Da es einige Missverständnisse über die Verwendung eines unbekannten Datentyps zu geben scheint: In den meisten Fällen stellen Sie eine Art “Registrierungs” -Funktion bereit. In einem typischen Fall übergeben Sie Zeiger an Funktionen, die alle Operationen ausführen können, die Sie für ein gespeichertes Element benötigen. Es generiert und gibt einen neuen Index zurück, der für den Wert verwendet wird, der den Typ identifiziert. Wenn Sie dann ein Objekt dieses Typs speichern möchten, setzen Sie seine Kennung auf den Wert, den Sie von der Registrierung erhalten haben, und wenn der Code, der mit den Objekten arbeitet, etwas mit diesem Objekt tun muss, ruft er die entsprechende Funktion über auf Zeiger, den Sie übergeben haben. In einem typischen Fall befinden sich diese Zeiger auf Funktionen in a struct, und es speichert (Zeiger auf) diese Strukturen einfach in einem Array. Der Identifikatorwert, den es von der Registrierung zurückgibt, ist nur der Index in das Array der Strukturen, in denen es diese bestimmte gespeichert hat.

Obwohl die Verwendung von union heutzutage nicht mehr üblich ist, da union für Ihr Nutzungsszenario definitiver ist, passt es gut. Im ersten Codebeispiel wird der Inhalt von Daten nicht verstanden.

Benutzer-Avatar
Tim Allmann

Ich bevorzuge den Weg der Union. Der Cast from Void* ist ein stumpfes Instrument und der Zugriff auf das Datum durch einen richtig getippten Zeiger gibt ein bisschen zusätzliche Sicherheit.

Benutzer-Avatar
Dan Olson

Wirf eine Münze. Union wird häufiger mit Nicht-Zeiger-Typen verwendet, daher sieht es hier etwas seltsam aus. Die explizite Typspezifikation, die es bereitstellt, ist jedoch eine anständige implizite Dokumentation. void* wäre in Ordnung, solange Sie immer wissen, dass Sie nur auf Zeiger zugreifen werden. Fangen Sie nicht an, dort ganze Zahlen einzufügen und sich auf sizeof(void*) == sizeof (int) zu verlassen.

Ich habe nicht das Gefühl, dass eine der beiden Möglichkeiten am Ende einen Vorteil gegenüber der anderen hat.

1090590cookie-checkUnion versus Void-Zeiger

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

Privacy policy