Umwandeln eines Strukturzeigers in einen anderen – C

Lesezeit: 6 Minuten

Benutzeravatar von Navaneeth KN
Navaneeth KN

Bitte beachten Sie den folgenden Code.

enum type {CONS, ATOM, FUNC, LAMBDA};

typedef struct{
  enum type type;
} object;

typedef struct {
  enum type type;
  object *car;
  object *cdr;
} cons_object;

object *cons (object *first, object *second) {
  cons_object *ptr = (cons_object *) malloc (sizeof (cons_object));
  ptr->type = CONS;
  ptr->car = first;
  ptr->cdr = second;
  return (object *) ptr;
}

In dem cons Funktion, variabel ptr ist vom Typ cons_object*. Aber im Rückgabewert wird es in Typ von umgewandelt object*.

  1. Ich frage mich, wie das möglich ist, weil cons_object und object sind verschiedene Strukturen.
  2. Gibt es irgendwelche Probleme, solche Dinge zu tun?

Irgendwelche Gedanken!

Dies ist in Ordnung und eine ziemlich übliche Technik zur Implementierung von “Objektorientierung” in C. Aufgrund des Speicherlayouts von structs ist in C wohldefiniert, solange die beiden Objekte das gleiche Layout haben, können Sie sicher Zeiger zwischen ihnen umwandeln. Das heißt, der Versatz der type Mitglied ist das gleiche in der object struct wie in der cons_object Struktur.

In diesem Fall ist die type Mitglied teilt der API mit, ob die object ist ein cons_object oder foo_object oder eine andere Art von Objekt, so dass Sie vielleicht so etwas sehen:

void traverse(object *obj)
{
    if (obj->type == CONS) {
        cons_object *cons = (cons_object *)obj;
        traverse(cons->car);
        traverse(cons->cdr);
    } else if (obj->type == FOO) {
        foo_object *foo = (foo_object *)obj;
        traverse_foo(foo);
    } else ... etc
}

Häufiger habe ich Implementierungen gesehen, bei denen die “Eltern” -Klasse als erstes Mitglied der “Kind” -Klasse definiert ist, wie folgt:

typedef struct {
    enum type type;
} object;

typedef struct {
    object parent;

    object *car;
    object *cdr;
} cons_object;

Dies funktioniert weitgehend auf die gleiche Weise, außer dass Sie eine starke Garantie haben, dass das Speicherlayout der untergeordneten “Klassen” das gleiche wie das der Eltern ist. Das heißt, wenn Sie ein Mitglied zur ‘Basis’ hinzufügen objectwird es automatisch von den Kindern aufgenommen und Sie müssen nicht manuell sicherstellen, dass alle Strukturen synchron sind.

  • Sie sollten erwähnen, dass dies legitimes C mit genau definiertem Verhalten ist und kein “Hack” oder Aufruf von “undefiniertem Verhalten”.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    22. September 2010 um 5:15 Uhr

  • Es wäre cool, wenn jemand einen Standard zitieren und erklären würde, warum das ursprüngliche Beispiel nicht der strengen Aliasing-Regel widerspricht, wie in dieser Antwort behauptet: stackoverflow.com/a/3766967/895245 Verschachtelte Strukturen werden auch erwähnt unter: stackoverflow.com/questions/ 8416417/…

    – Ciro Santilli OurBigBook.com

    2. März 2016 um 16:33 Uhr


  • Ein passendes Speicherlayout tut es nicht Machen Sie es zulässig, zwischen Strukturzeigern gemäß der Strict-Aliasing-Regel umzuwandeln. Es gibt tatsächlich einen PEP zur Behebung dieses Problems in der Objektimplementierung von CPython: python.org/dev/peps/pep-3123. Offensichtlich erscheinen diese Besetzungen vernünftig und funktionieren fast immer, aber UB ist UB.

    – Praxeolit

    8. Dezember 2016 um 2:27 Uhr


  • Sehr spät zur Party, aber ich stimme auch zu, dass diese Antwort irreführend ist – zwei nicht verwandte Strukturen mit demselben Layout können nicht alias einander.

    – Oliver Charlesworth

    8. Dezember 2017 um 9:01 Uhr


  • @OliverCharlesworth: Die Antwort ist richtig für die von Dennis Ritchie erfundene Sprache und auch für Dialekte, für deren Verarbeitung die überwiegende Mehrheit der Compiler konfiguriert werden kann. Der Standard degradiert diese Fähigkeit auf den Status einer „beliebten Erweiterung“, die von Implementierungen nicht unterstützt werden muss, trifft jedoch kein Urteil darüber, ob Dialekte, die sie weglassen, für einen bestimmten Zweck als geeignet angesehen werden sollten.

    – Superkatze

    8. Oktober 2018 um 22:01 Uhr

Benutzeravatar von Jeff Mercado
Jeff Mercado

Um Deans Antwort zu ergänzen, hier etwas über Zeigerkonvertierungen im Allgemeinen. Ich habe vergessen, was der Begriff dafür ist, aber ein Pointer-to-Pointer-Cast führt keine Konvertierung durch (genauso wie int to float). Es ist einfach eine Neuinterpretation der Bits, auf die sie zeigen (alles zum Vorteil des Compilers). “Zerstörungsfreie Konvertierung”, denke ich, war es. Die Daten ändern sich nicht, nur wie der Compiler interpretiert, worauf gezeigt wird.

z.B,
Wenn ptr ist ein Zeiger auf ein objectweiß der Compiler, dass es ein Feld mit einem bestimmten Offset namens gibt type des Typs enum type. Andererseits wenn ptr wird in einen Zeiger auf einen anderen Typ umgewandelt, cons_objectwieder wird es wissen, wie man auf Felder der zugreift cons_object jeweils mit ihren eigenen Offsets in ähnlicher Weise.

Stellen Sie sich zur Veranschaulichung das Speicherlayout für a vor cons_object:

                    +---+---+---+---+
cons_object *ptr -> | t | y | p | e | enum type
                    +---+---+---+---+
                    | c | a | r |   | object *
                    +---+---+---+---+
                    | c | d | r |   | object *
                    +---+---+---+---+

Das type Feld hat Offset 0, car ist 4, cdr ist 8. Um auf das Autofeld zuzugreifen, muss der Compiler lediglich hinzufügen 4 zum Zeiger auf die Struktur.

Wenn der Zeiger in einen Zeiger auf eine umgewandelt wurde object:

                    +---+---+---+---+
((object *)ptr)  -> | t | y | p | e | enum type
                    +---+---+---+---+
                    | c | a | r |   |
                    +---+---+---+---+
                    | c | d | r |   |
                    +---+---+---+---+

Alles, was der Compiler wissen muss, ist, dass es ein Feld namens gibt type mit Offset 0. Was auch immer im Speicher ist, ist im Speicher.

Die Zeiger müssen nicht einmal in irgendeiner Beziehung stehen. Sie können einen Zeiger auf ein haben int und wandeln Sie es in einen Zeiger auf ein um cons_object. Wenn Sie auf die zugreifen würden car Feld, es ist genau wie jeder gewöhnliche Speicherzugriff. Es hat einen gewissen Versatz vom Anfang der Struktur. In diesem Fall ist unbekannt, was sich an diesem Speicherort befindet, aber das ist unwichtig. Um auf ein Feld zuzugreifen, wird nur der Offset benötigt und diese Informationen finden sich in der Definition des Typs.

Ein Zeiger auf ein int zeigt auf einen Speicherblock:

                        +---+---+---+---+
int             *ptr -> | i | n | t |   | int
                        +---+---+---+---+

Casting zu a cons_object Zeiger:

                        +---+---+---+---+
((cons_object *)ptr) -> | i | n | t |   | enum type
                        +---+---+---+---+
                        | X | X | X | X | object *
                        +---+---+---+---+
                        | X | X | X | X | object *
                        +---+---+---+---+

  • fantastisch!. Danke für den ausführlichen Beitrag!

    – Navaneeth KN

    22. September 2010 um 5:15 Uhr

  • “aber eine Zeiger-zu-Zeiger-Umwandlung führt keine Konvertierung durch (genauso wie int zu Float). Es ist einfach eine Neuinterpretation der Bits, auf die sie zeigen (alles zum Vorteil des Compilers)” Das ist falsch. Zeiger auf verschiedene Typen können unterschiedlich dargestellt werden, sie müssen nicht einmal die gleiche Größe haben. c-faq.com/null/machexamp.html

    – Karl S

    25. Juli 2018 um 17:39 Uhr

Die Verwendung separater Strukturen verstößt gegen die strenge Aliasing-Regel und ist ein undefiniertes Verhalten: http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

Die Verwendung einer eingebetteten Struktur wie in Deans letztem Beispiel ist in Ordnung.

  • AFAIK, diese Antwort ist richtig und die (derzeit) akzeptierte Antwort ist falsch. Siehe auch stackoverflow.com/questions/47710585/…

    – Oliver Charlesworth

    8. Dezember 2017 um 9:05 Uhr

1389650cookie-checkUmwandeln eines Strukturzeigers in einen anderen – C

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

Privacy policy