Ausrichtung des C-Struktur-Vererbungszeigers

Lesezeit: 11 Minuten

Benutzeravatar von thndrwrks
thndrwrks

Hintergrund

Ich habe hauptsächlich zu Lernzwecken eine grundlegende Datenstruktur für verknüpfte Listen erstellt. Ein Ziel der Liste war, dass sie mit unterschiedlichen Datenstrukturen umgehen kann. Daher habe ich mich an der Strukturzusammensetzung versucht, um “Vererbung” in C zu simulieren. Hier sind die Strukturen, die die Grundlage für meine verknüpfte Liste bilden.

typedef struct Link {
    struct Link* next;
    struct Link* prev;
} Link;

typedef Link List;

In meiner Implementierung habe ich mich für einen Sentinel-Knoten entschieden, der sowohl als Kopf als auch als Ende der Liste dient (weshalb Link == List).

Damit die Liste tatsächlich Daten verarbeitet, enthält eine Struktur einfach die Link-Struktur als erstes Mitglied:

typedef struct {
    Link link;
    float data;
} Node;

Die verknüpfte Liste sieht also so aus.

         ┌───┬───┬───┐     ┌───┬───┐     ┌───┬───┬───┐     
... <--->│ P │ N │ D │<--->│ P │ N │<--->│ P │ N │ D │<---> ... 
         └───┴───┴───┘     └───┴───┘     └───┴───┴───┘
         End Node          myList        First Node

List myList;
Node node1 = {{}, 1.024};
....
Node nodeN = {{}, 3.14};

list_init(&myList) // myList.next = &myList; myList.prev = &myList;
list_append(&myList, &node1);
....
list_append(&myList, &nodeN);

Frage

Um diese Liste zu durchlaufen a Node Zeiger zeigt zunächst auf die Erster Knoten. Er durchquert dann die Liste, bis er wieder auf den Wächter zeigt, und stoppt dann.

void traverse()
{
    Node* ptr;
    for(ptr = myList.next; ptr != &myList; ptr = ptr->link.next)
    {
        printf("%f ", ptr->data);
    }
}

Meine Frage bezieht sich auf die Leitung ptr != &myList. Gibt es ein Zeigerausrichtungsproblem mit dieser Zeile?

Die for-Schleife erzeugt korrekt die Warnungen: (warning: assignment from incompatible pointer type und warning: comparison of distinct pointer types lacks a cast), das zum Schweigen gebracht werden kann, indem man tut, was es sagt, und zu ihm wirkt Node*. Aber ist das ein DumbThingToDo™? Ich greife nie zu ptr->data wenn es darauf hindeutet &myList da die Schleife einmal endet ptr == &myList.

TLDR

In C-Strukturen a Base* kann auf a hinweisen Derived wenn Base ist das erste Mitglied Derived. Kann A Derived* zeigen auf a Base wenn keine Derived auf bestimmte Mitglieder zugegriffen wird?

BEARBEITEN: Relevante Funktionsaufrufe durch ihren entsprechenden Inline-Code ersetzt.

  • Unabhängig von der Frage selbst wollte ich nur sagen, dass die Präsentation ist hervorragend.

    – WhozCraig

    27. Januar 2015 um 20:52 Uhr

  • Ich glaube nicht, dass Sie ein Problem mit der Zeigerausrichtung haben werden, aber ich würde fragen: Warum nicht make List ein Pseudonym für Node und ignoriere die einfach data Mitglied für List? Oder machen Sie es einfach ein Node direkt (definieren Node myList; Anstatt von List myList;) Das wäre ein saubererer Weg, um die Sorge um das Zeigerwerfen zu vermeiden. Und ich stimme dem anderen Kommentar zu: Gute Arbeit, das Problem klar zu benennen. (+1)

    – Schleicher

    27. Januar 2015 um 20:54 Uhr


  • &myList ist kein Node*es ist ein Zeiger auf a Node *oder ein Node **. Ich denke, Sie brauchen eine zweite Variable, um zu setzen list_first(&myList) oder einfach anrufen list_first(&myList) in dem for und hoffen, dass der Compiler gut optimiert wird.

    – Brian McFarland

    27. Januar 2015 um 21:03 Uhr

  • Ich habe Inline list_first() und link_next() in meiner Frage.

    – thndrwrks

    27. Januar 2015 um 21:42 Uhr

  • @lurker Ich habe überlegt, Node myList zu definieren, aber es ist einfach scheint dass eine Liste vom Typ sein sollte List ;).

    – thndrwrks

    27. Januar 2015 um 21:43 Uhr

Benutzeravatar von midor
mittel

Kudos für Ihre Präsentation.

Ich denke, Ihre Implementierung sollte gut funktionieren, da C garantiert, dass die Adresse einer Struktur die Adresse ihres ursprünglichen Mitglieds ist. Abgesehen von den Aussagen, die C über die Ausrichtung von Strukturmitgliedern macht, sollte diese Garantie bedeuten, dass, solange Ihre Implementierung Link immer als erstes Mitglied setzt, dies keine Ausrichtungsprobleme verursachen sollte.

ab hier: C99 §6.7.2.1:

13 Innerhalb eines Strukturobjekts haben die Mitglieder, die keine Bitfelder sind, und die Einheiten, in denen sich Bitfelder befinden, Adressen, die in der Reihenfolge aufsteigen, in der sie deklariert werden. Ein geeignet umgewandelter Zeiger auf ein Strukturobjekt zeigt auf sein Anfangselement (oder, wenn dieses Element ein Bitfeld ist, dann auf die Einheit, in der es sich befindet) und umgekehrt. Innerhalb eines Strukturobjekts kann es unbenannte Auffüllungen geben, jedoch nicht an seinem Anfang

Das sollte das sein, was du damit sagen wolltest Base * und Derived *obwohl es so etwas in reinem C nicht gibt. Sie sind nur Strukturen, die zufällig das gleiche Speicherlayout haben.

Allerdings finde ich es etwas spröde, das so zu implementieren, da Node und Link direkt voneinander abhängen. Wenn Sie die Struktur von Node ändern würden, würde Ihr Code ungültig. Im Moment sehe ich keinen Sinn darin, ein Extra zu haben struct Linkabgesehen davon, dass Sie einfach einen neuen Knoten für einen neuen Typ unter Wiederverwendung von Link schreiben können.

Es gibt tatsächlich eine Linked-List-Implementierung, die mir sofort in den Sinn kam, als ich Ihren Beitrag sah, und die auf eine Weise funktioniert, die der Art und Weise, wie Sie Ihre Liste verwenden möchten, sehr ähnlich ist: die Kernel-Liste

Es verwendet dasselbe Listenelement (list_head):

struct list_head {
    struct list_head *next, *prev;
};

Es enthält dieses Funktionsmakro:

#define list_for_each_entry(pos, head, member)                          \
      for (pos = list_first_entry(head, typeof(*pos), member);        \
           &pos->member != (head);                                    \
           pos = list_next_entry(pos, member))

Wenn Sie sich die Art und Weise ansehen, wie das Makro implementiert ist, werden Sie feststellen, dass es eine Iteration über die Einträge einer Liste anbietet, ohne etwas über das Layout der Einträge zu wissen, in denen die Liste enthalten ist. Vorausgesetzt, ich interpretiere Ihre Absicht richtig, denke ich, dass dies der Fall ist so wie du es haben möchtest.

  • Upvoted, obwohl es mich seit einiger Zeit ärgert, dass C99 nicht sagt, welche Art von Konvertierung eine “geeignete” Konvertierung wäre. (Ja, es bedeutet wahrscheinlich “wenn es in den entsprechenden Typ konvertiert wird”, aber warum sagt es das dann nicht einfach?)

    – davmac

    27. Januar 2015 um 22:38 Uhr

  • Es ist sehr lustig, dass du das auch denkst. Ich habe lange überlegt, einen Kommentar dazu zu schreiben. Für die OP-Frage denke ich jedoch, dass die Tatsache, dass der Zeiger auf das ursprüngliche Mitglied und der Zeiger auf die Struktur selbst identisch sein sollten, wirklich wichtig ist.

    – mittel

    27. Januar 2015 um 23:02 Uhr

  • In der Praxis ja. Es gibt jedoch ein Problem mit dem Kopf / Schwanz in der Frage von OP. Denn diese Initiale Link Objekt ist nicht Teil von a Node (im Gegensatz zum Rest der Liste) die Zeigerkonvertierung in ptr = ptr->link.next ist nicht definiert (6.7.2.1p13 kann nicht angewendet werden). In der Praxis bezweifle ich, dass dies jemals ein Problem verursachen wird.

    – davmac

    28. Januar 2015 um 7:30 Uhr


Benutzeravatar von ouah
ouah

In C-Strukturen kann eine Base* auf ein Derived zeigen, wenn Base das erste Element in Derived ist. Kann ein Derived* auf eine Base zeigen, wenn auf keines der Derived-spezifischen Mitglieder zugegriffen wird?

Wenn Sie meinen “kann ein abgeleitetes * auf eine beliebige Basis zeigen (einschließlich einer, die kein erstes Mitglied eines abgeleiteten Objekts ist)”, dann: Technisch gesehen nein. Mein Verständnis von Fehlerbericht Nr. 74 Die Ausrichtungsanforderungen können unterschiedlich sein.

F: Wenn eine Struktur ein Feld vom Typ t hat, können sich die Ausrichtungsanforderungen des Felds von den Ausrichtungsanforderungen von Objekten desselben Typs unterscheiden, die keine Mitglieder von Strukturen sind? Wenn die Antwort auf (a) „Ja“ lautet, dann sollte gegebenenfalls davon ausgegangen werden, dass die verbleibenden Fragen sowohl für Objekte innerhalb von Strukturen als auch für Objekte außerhalb von Strukturen gestellt wurden.

A: In Unterabschnitt 6.1.2.5 heißt es: „… Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen müssen die gleichen Darstellungs- und Ausrichtungsanforderungen haben.“ In Unterabschnitt 6.5.2.1 heißt es: „Jedes Nicht-Bitfeld-Mitglied einer Struktur oder eines Vereinigungsobjekts wird in einer implementierungsdefinierten Weise ausgerichtet, die seinem Typ entspricht.“ Und später: ‘Daher kann es innerhalb eines Strukturobjekts unbenannte Auffüllungen geben, … je nach Bedarf, um die entsprechende Ausrichtung zu erreichen.’ a) Es ist möglich, dass eine Implementierung verallgemeinerte Anforderungen angibt, um Abschnitt 6.1.2.5 zu erfüllen. Diese Anforderungen können unter Verwendung des implementierungsdefinierten Verhaltens, das in Unterabschnitt 6.5.2.1 zur Verfügung gestellt wird, weiter verstärkt werden. Ja, die Ausrichtungsanforderungen können unterschiedlich sein.

  • Während dies wahrscheinlich für einen möglichst standardkonformen Code gilt, bezweifle ich sehr, dass es eine einzige praktische und sogar halb populäre Architektur gibt, deren ABI dies tatsächlich tut.

    – Dolda2000

    27. Januar 2015 um 21:10 Uhr

  • @davmac Die Zeile “unbenanntes Auffüllen” ist Teil der Einführung für alle Antworten im DR, und ich denke, das Zitat “unbenanntes Auffüllen” wird für die anderen Antworten dieses DR verwendet, die sich speziell auf das Auffüllen beziehen. Bezüglich der stärker Anforderung, die Sie erwähnen, sehe ich nichts, was in der vorliegenden DR in diese Richtung geht, es steht nur geschrieben kann anders sein.

    – au

    27. Januar 2015 um 21:25 Uhr


  • Ich denke, wenn der Fehlerbericht Nr. 74 auf die angegebenen spezifischen Fragen zutrifft, ist er falsch. Denken Sie daran, dass C99 §6.7.2.1 auch den Satz enthält “und umgekehrt” und ich sehe nicht, ob oder wie das Qualifizieren der zweiten Frage mit “wenn auf keine der abgeleiteten spezifischen Mitglieder zugegriffen wird” dies ändert.

    – Greg A. Woods

    7. März 2016 um 1:46 Uhr

  • @GregA.Woods, der auf ein “abgeleitetes spezifisches Mitglied” zugreift, erfordert, dass ein abgeleitetes Objekt vorhanden ist; Die Frage stellt sich, ob ein “Derived*” “auf eine Basis zeigen” kann (es existiert kein abgeleitetes Objekt). Ich glaube, die Einschränkung ist dazu da, um deutlich zu machen, dass kein Zugriff über die erfolgt Derived*, und dass es nur darum geht, ob ein solcher Zeiger existieren kann, und nicht darum, ob er dereferenziert werden kann. §6.7.2.1 sagt nicht, ob eine Umwandlung von „Derived*“ in „Base*“ immer eine „geeignete Konvertierung“ ist, z. ob es notwendigerweise gewährt, dass §6.3.2.3 Ausrichtungsanforderungen erfüllt sind.

    – davmac

    7. März 2016 um 13:49 Uhr


  • Erstens ist das nicht das, was die Frage stellt. Zweitens müssen wir angesichts der Frage annehmen, dass a Derived Die Existenz eines Objekts wird durch die Tatsache impliziert, dass ein Zeiger darauf einen Nicht-NULL-Wert hat. Dabei spielt es keine Rolle, ob der Zugriff im Rahmen der Frage erfolgt oder nicht. Außerdem kann jeder moderne Optimierer, der sein Geld wert ist, dieses Casting des Zeigers extrapolieren, um auf a zu zeigen Base Objekt, und vermutlich verwenden Sie es dann, um auf a zuzugreifen Base Objekt impliziert dies eindeutig das erstes Mitglied der Derived Auf das Objekt wird tatsächlich zugegriffen, da die einzige Manipulation des Zeigers darin besteht, ihn umzuwandeln.

    – Greg A. Woods

    7. März 2016 um 17:54 Uhr

Ich habe dies in den Kommentaren zu Ouahs Antwort geschrieben, aber ich werde es als eigenständige Antwort präsentieren.

Es ist wahr, wie ouah den Code in Ihrer Frage schreibt könnte Theoretisch ein Ausrichtungsproblem haben, wenn Sie sich an den C-Standard halten. Ich glaube jedoch nicht, dass es eine einzige Architektur gibt, auf der Ihr Code wahrscheinlich (oder unwahrscheinlich) ausgeführt wird, deren ABI solche Ausrichtungsmuster aufweist. Zumindest keine, die ich kenne, mit Sicherheit, und dazu gehören Erfahrungen mit ein paar verschiedenen Desktop- und eingebetteten Architekturen. Ich kann mir auch keine Architektur vorstellen, die solchen Alignment-Eigenschaften etwas abgewinnen würde.

Es ist auch erwähnenswert, dass dieses Muster in der Praxis tatsächlich ziemlich häufig verwendet wird; Es ist wie die Tatsache, dass man immer passen kann ints in Hinweisen, da es durch keinen plattformübergreifenden Standard garantiert wird, aber es gibt praktisch keine Plattform, auf der es nicht wahr ist, und die Leute tun es die ganze Zeit.

  • “Ich kann mir auch keine Architektur vorstellen, die von solchen Alignment-Eigenschaften profitieren würde” – bedenken Sie, dass Wortgrößenerhöhungen manchmal ein entsprechendes Alignment erfordern. Auf 32-Bit-x86 erfordern SSE2-Erweiterungen eine 8-Byte-Ausrichtung, um 64-Bit-Werte effizient verarbeiten zu können; Zeiger erfordern jedoch nur eine 4-Byte-Ausrichtung. Es ist daher zur Zeit durchaus möglich, nicht-triviale Strukturen mit unterschiedlichen Ausrichtungsanforderungen zu haben. (Andererseits erzwingt x86 natürlich keine Ausrichtung beim Laden des Zeigers, sodass der Code von OP wahrscheinlich immer noch in Ordnung wäre).

    – davmac

    9. März 2016 um 20:57 Uhr


  • @davmac: Das stimmt zwar allgemeiner, aber ich sehe nicht, dass es auf den Fall von OP zutrifft, auf den er nur zugreift Link Mitglieder über Node Zeiger und nicht umgekehrt. Es könnte Ausrichtungsprobleme der Art geben, die Sie beim Zugriff erwähnen Node Mitglieder über Link Zeiger, aber OP vermeidet das ausdrücklich.

    – Dolda2000

    10. März 2016 um 6:03 Uhr


  • Ich spreche von Ausrichtungsanforderungen, nicht von Aliasing-Einschränkungen. Unabhängig davon, ob Sie Zugriff haben oder nicht, Node möglicherweise eine strengere Ausrichtungsanforderung als ListWiedergabe ptr = ptr->link.next wie nicht streng konform. Wie Sie jedoch sagen, ist es unwahrscheinlich, dass dies auf den meisten realen Architekturen zu Problemen führt (auf die ich oben anspiele: x86 erzwingt keine Ausrichtung beim Laden des Zeigers).

    – davmac

    10. März 2016 um 9:37 Uhr

  • @davmac: Genau das meinte ich aber. Node kann strengere Ausrichtungsanforderungen haben als Listaber nicht umgekehrt, und da keine Datenstruktur, die entsprechend ausgerichtet wurde List wird zugegriffen als Node, das sollte kein Problem sein. Nein? Wenngleich ptr->link.next wird über a dereferenziert Link Zeiger, es wird noch entsprechend zugeordnet worden sein Node Ausrichtung.

    – Dolda2000

    14. März 2016 um 5:32 Uhr

  • “da auf keine nach Liste ausgerichtete Datenstruktur als Knoten zugegriffen wird” – nicht korrekt; Überprüfen Sie die Frage von OP – In meiner Implementierung habe ich mich für einen Sentinel-Knoten entschieden, der sowohl als Kopf als auch als Ende der Liste dient; dieser Sentinel-Knoten ist als a definiert List eher als ein Node (List myList;) und wird dann ggf. über angesprochen ptr = ptr->link.next in der for-Schleife in der traverse Methode.

    – davmac

    15. März 2016 um 10:41 Uhr


1408220cookie-checkAusrichtung des C-Struktur-Vererbungszeigers

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

Privacy policy