Ist &*p gültiges C, vorausgesetzt, dass p ein Zeiger auf einen unvollständigen Typ ist?

Lesezeit: 6 Minuten

Ist das folgende Beispiel eine gültige vollständige Übersetzungseinheit in C?

struct foo;

struct foo *bar(struct foo *j)
{
    return &*j;
}

struct foo ist ein unvollständiger Typ, aber ich kann im C-Standard kein explizites Verbot der Dereferenzierung eines unvollständigen Typs finden. Insbesondere heißt es in §6.5.3.2:

Das Unäre & Operator liefert die Adresse seines Operanden. Wenn der Operand vom Typ ”Typ” ist, hat das Ergebnis den Typ ”Zeiger auf Typ”. Wenn der Operand das Ergebnis eines unären ist * Operator, weder dieser Operator noch der & -Operator wird ausgewertet und das Ergebnis ist so, als ob beide weggelassen würden, außer dass die Einschränkungen für die Operatoren weiterhin gelten und das Ergebnis kein L-Wert ist.

Die Tatsache, dass das Ergebnis kein Lvalue ist, ist nicht relevant – Rückgabewerte müssen dies nicht sein. Die Einschränkungen bei der * Operator sind einfach:

Der Operand des unären *-Operators muss vom Typ Zeiger sein.

und auf der & Betreiber sind:

Der Operand des unären & Operator soll entweder ein Funktionsbezeichner, das Ergebnis von a [] oder unär * -Operator oder ein Lvalue, der ein Objekt bezeichnet, das kein Bitfeld ist und nicht mit dem deklariert wird register Speicherklassenbezeichner.

Beides ist hier trivialerweise erfüllt, also dürfte das Ergebnis gerade eben äquivalent sein return j;.

gcc 4.4.5 kompiliert diesen Code jedoch nicht. Es gibt stattdessen den folgenden Fehler:

y.c:5: error: dereferencing pointer to incomplete type

Ist das ein Fehler in gcc?

  • Ich denke, Sie haben Recht. Dies wird sicherlich nicht das erste oder letzte sein…

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

    4. August 2011 um 7:11 Uhr

  • Technisch gesehen hat nur C99 diesen Wortlaut. C90 erwähnt das nicht & und * heben sich gegenseitig auf. Für C99 würde ich Ihrer Analyse jedoch zustimmen. Aber da gcc nicht behauptet, vollständig C99-kompatibel zu sein, bin ich über diese Fehlermeldung nicht allzu überrascht.

    – Sander DeDycker

    4. August 2011 um 8:25 Uhr

Benutzer-Avatar
Jens Gustedt

Ja, ich denke es ist ein Bug. Auch lvalues ​​von unvollständigen Typen, also *jscheinen je nach Kontext erlaubt zu sein:

6.3.2.1 … Ein lvalue ist ein Ausdruck mit einem Objekttyp oder einem unvollständigen Typ außer void

Grundsätzlich sollte dies funktionieren, solange Sie nichts mit einem solchen Wert tun, der etwas über die Struktur von wissen muss struct. Wenn Sie also nicht auf das Objekt zugreifen oder nach seiner Größe fragen, ist dies legal.

  • Ja, das wäre die nächste logische Frage… zum Beispiel ein leerer Ausdruck wie (void)*j sollte meiner Lektüre nach auch erlaubt sein.

    – Café

    4. August 2011 um 10:37 Uhr

Benutzer-Avatar
Jonathan Leffler

Der C99-Standard (ISO/IEC 9899:1999) beschreibt das Verhalten:

§6.5.3.2 Adress- und Indirektionsoperatoren

Der unäre &-Operator gibt die Adresse seines Operanden zurück. Wenn der Operand vom Typ ”Typ” ist, hat das Ergebnis den Typ ”Zeiger auf Typ”. Wenn der Operand das Ergebnis eines unären *-Operators ist, wird weder dieser Operator noch der &-Operator ausgewertet und das Ergebnis ist so, als ob beide weggelassen würden, außer dass die Einschränkungen für die Operatoren weiterhin gelten und das Ergebnis kein L-Wert ist.

Das bedeutet, dass &*j ist äquivalent zu j.

Jedoch, j soll ein Zeiger auf ein Objekt sein, und es ist nur ein Zeiger auf einen unvollständigen Typ, wie GCC 4.4.5 sagt.

§6.3.2.3 Zeiger

Ein Zeiger auf void kann in oder von einem Zeiger auf einen beliebigen unvollständigen oder Objekttyp konvertiert werden. Ein Zeiger auf einen unvollständigen oder Objekttyp kann in einen Zeiger auf void und wieder zurück umgewandelt werden; das Ergebnis soll mit dem ursprünglichen Zeiger verglichen werden.

Beachten Sie, dass es zwischen einem Objekttyp und einem unvollständigen Typ unterscheidet; dies kommt im Standard häufig vor.

Diese Beobachtung in der Frage ist also falsch:

Beide sind hier trivial zufrieden,

Die Variable j ist kein Zeiger auf ein Objekt; es ist ein Zeiger auf einen unvollständigen Typ, der kein Objekt ist.


§6.2.5 Typen

[…] Typen werden unterteilt in Objekttypen (Typen, die Objekte vollständig beschreiben), Funktionstypen (Typen, die Funktionen beschreiben) und unvollständige Typen (Typen, die Objekte beschreiben, denen jedoch Informationen fehlen, die zum Bestimmen ihrer Größe erforderlich sind).

  • Der Teil, der in Ihrer Analyse zu fehlen scheint, ist wo ein Zeiger auf ein Objekt ist ausdrücklich erforderlich. Die einzige Einschränkung für den Operanden der * Operator scheint zu sein, dass er einen “Zeigertyp” hat, der sowohl Zeiger auf Objekte als auch unvollständige Typen umfasst.

    – Café

    4. August 2011 um 10:32 Uhr

  • ich denke, dass j still ist ein Zeiger auf ein Objekt. Nur dass sein Typ unvollständig ist. Der Standard spricht beispielsweise explizit von lvalues ​​vom Typ unvollständig. Das Zeigen auf ein Objekt hängt nur davon ab, dass dieses richtig zugeordnet ist (Größe und Ausrichtung).

    – Jens Gustedt

    4. August 2011 um 14:05 Uhr

  • @Jonathan, sagt es nicht genau dasselbe wie ich? “unvollständige Typen” sind “Typen, die Objekte beschreiben, denen aber Informationen fehlen …”. Also ist insbesondere ein Zeiger auf einen unvollständigen Typ ein Zeiger auf ein Objekt. Und die Dereferenzierung eines solchen Zeigers erzeugt einen lvalue. Und solche lvalues ​​unvollständigen Typs werden an anderer Stelle ausdrücklich erwähnt.

    – Jens Gustedt

    4. August 2011 um 20:48 Uhr


  • @jens: Nein. Es heißt, dass es Objekttypen gibt, die vollständig sind; es gibt unvollständige Typen (die per Definition von „Partition“ nicht dasselbe sind wie Objekttypen) und es gibt Funktionstypen. Objekttypen unterscheiden sich also von unvollständigen Typen und sind nicht mit diesen identisch.

    – Jonathan Leffler

    4. August 2011 um 20:49 Uhr

  • @Jonathan nein, das steht nicht drin. Der Teil, den Sie zitieren, sagt “Typen, die Objekte beschreiben”, es gibt nichts Mehrdeutiges.

    – Jens Gustedt

    4. August 2011 um 20:51 Uhr

Benutzer-Avatar
Aaron Digulla

Ja. Zeiger in C haben normalerweise die gleiche Größe (auf einigen eingebetteten Systemen können sie unterschiedlich sein). Das bedeutet, dass der Compiler dafür den richtigen Assembler-Code generieren kann, obwohl der Typ “unbekannt” ist.

Mit diesem Ansatz können Sie die interne Datenstruktur vollständig nach außen verbergen. Verwenden ein typedef einen Zeiger auf eine Struktur zu deklarieren und die Struktur nur in Ihren internen Header-Dateien zu deklarieren (dh den Dateien, die nicht Teil Ihrer öffentlichen API sind).

Der Grund, warum sich gcc 4.4.5 beschwert, ist folgender: Wenn Sie Zeiger auf den unvollständigen Typ außerhalb der Implementierung verwenden, sollte es funktionieren. Aber der Code ist Teil der Implementierung und hier möchten Sie wahrscheinlich den vollständigen Typ haben.

  • Es ist nicht richtig, dass alle Zeiger in C die gleiche Größe haben. Es gibt nur sehr wenige Plattformen, auf denen das passiert, aber zB bei Embedded Systems mit Harvard-Architektur findet man unterschiedliche Zeigergrößen für Daten- und Funktionszeiger.

    – flolo

    4. August 2011 um 8:12 Uhr

  • @flolo: richtig. Dies wurde sogar in einer kürzlich gestellten Frage hier diskutiert. Jemand hat einen Link zu einigen Plattformen gepostet, auf denen Zeiger unterschiedlichen Typs unterschiedliche Größen hatten.

    – Rudy Velthuis

    4. August 2011 um 8:58 Uhr

  • der relevante Teil des C99-Standards ist Abschnitt 6.2.5, §27: „Alle Zeiger auf Strukturtypen müssen untereinander die gleichen Darstellungs- und Ausrichtungsanforderungen haben.“; dies gilt nicht für beliebige Zeigertypen

    – Christoph

    4. August 2011 um 9:14 Uhr

1362750cookie-checkIst &*p gültiges C, vorausgesetzt, dass p ein Zeiger auf einen unvollständigen Typ ist?

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

Privacy policy