Wie ist es legal, einen undefinierten Typ innerhalb einer Struktur zu referenzieren?

8

Als ich eine weitere Frage beantwortet habe, bin ich auf einen Code wie diesen gestoßen, den gcc kompromisslos kompiliert.

%Vor%

Dies ist das Mittel, das ich immer benutzt habe, um Typen zu konstruieren, die auf sich selbst verweisen (zB verknüpfte Listen), aber ich dachte immer, man müsste die Struktur benennen , Referenz. Mit anderen Worten, Sie konnten xyz *z nicht innerhalb der Struktur verwenden, da der typedef an diesem Punkt noch nicht abgeschlossen ist.

Aber in diesem Beispiel wird nicht die Struktur benannt und trotzdem kompiliert. Ich dachte ursprünglich, dass im Compiler etwas schwarze Magie vor sich ging, die den obigen Code automatisch umsetzte, weil die Namen der Struktur und des typedef identisch waren.

Aber diese kleine Schönheit funktioniert auch:

%Vor%

Was fehlt mir hier? Dies scheint ein klarer Verstoß zu sein, da nirgends ein struct NOTHING_LIKE_xyz -Typ definiert ist.

Wenn ich es von einem Zeiger zu einem tatsächlichen Typ ändere, erhalte ich den erwarteten Fehler:

%Vor%

Wenn ich struct lösche, erhalte ich einen Fehler ( parse error before "NOTHING ... ).

Ist das in ISO C erlaubt?

Update: Ein struct NOSUCHTYPE *variable; kompiliert auch, so dass es nicht nur innerhalb Strukturen ist, wo es gültig zu sein scheint. Ich kann nichts im c99-Standard finden, der diese Nachsicht für Strukturzeiger erlaubt.

    
paxdiablo 24.05.2010, 06:40
quelle

7 Antworten

6

Die Teile des C99-Standards, nach denen Sie suchen, sind 6.7.2.3, Absatz 7:

  

Wenn ein Typspezifizierer des Formulars    struct-or-union identifier tritt auf   außer als Teil von einem der oben genannten   Formen und keine andere Erklärung der   Kennung als ein Tag ist dann sichtbar   es erklärt eine unvollständige Struktur oder   Unionstyp und deklariert den   Bezeichner als Tag dieses Typs.

... und 6.2.5 Absatz 22:

  

Eine Struktur oder ein Unionstyp von unbekannt   Inhalt (wie in 6.7.2.3 beschrieben) ist   ein unvollständiger Typ. Es ist vollbracht,   für alle Deklarationen dieses Typs, by   die gleiche Struktur oder Vereinigung erklären   Tag mit seinem definierenden Inhalt später in   der gleiche Umfang.

    
caf 24.05.2010, 07:11
quelle
7

Wie die Warnung im zweiten Fall sagt, ist struct NOTHING_LIKE_xyz ein unvollständiger Typ , wie void oder Arrays unbekannter Größe. Ein unvollständiger Typ kann nur als ein Typ angezeigt werden, auf den verwiesen wird, mit Ausnahme für Arrays unbekannter Größe, die als letztes Mitglied einer Struktur zulässig sind, wodurch die Struktur selbst in diesem Fall zu einem unvollständigen Typ wird. Der folgende Code kann keinen Zeiger auf einen unvollständigen Typ dereferenzieren (aus gutem Grund).

Unvollständige Typen können einige Arten von Datenkapselung in C anbieten ... Der entsprechende Abschnitt in Ссылка scheint eine gute Erklärung zu sein.

    
Pascal Cuoq 24.05.2010 06:45
quelle
2

Der erste und der zweite Fall sind wohldefiniert, weil die Größe und Ausrichtung eines Zeigers bekannt ist. Der C-Compiler benötigt nur die Größe und die Ausrichtung, um eine Struktur zu definieren.

Der dritte Fall ist ungültig, weil die Größe dieser tatsächlichen Struktur unbekannt ist.

Aber Vorsicht, dass der erste Fall logisch sein muss, müssen Sie der Struktur einen Namen geben:

%Vor%

andernfalls werden die äußere Struktur und die *z als zwei verschiedene Strukturen betrachtet.

Der zweite Fall hat einen populären Anwendungsfall, bekannt als "opaque pointer" (pimpl) . Zum Beispiel könnten Sie eine Wrapper-Struktur als

definieren %Vor%

in der Kopfzeile und dann in einem der .c ,

%Vor%

Der Vorteil ist, dass .c nicht mit den Interna des Objekts verwechselt werden kann. Es ist eine Art Kapselung.

    
kennytm 24.05.2010 06:55
quelle
1

Sie müssen es benennen. In diesem:

%Vor%

kann nicht auf sich selbst zeigen, da sich z auf einen anderen vollständigen Typ bezieht, nicht auf die unbenannte Struktur, die Sie gerade definiert haben. Versuchen Sie Folgendes:

%Vor%

Sie erhalten eine Fehlermeldung über inkompatible Typen.

    
R Samuel Klatchko 24.05.2010 06:55
quelle
1

Nun ... Alles was ich sagen kann ist, dass Ihre vorherige Annahme falsch war. Jedes Mal, wenn Sie ein struct X -Konstrukt (für sich selbst oder als Teil einer größeren Deklaration) verwenden, wird es als Deklaration eines Strukturtyps mit einem Struktur-Tag X interpretiert. Es könnte eine erneute Deklaration eines zuvor deklarierten Strukturtyps sein. Oder es kann eine allererste Deklaration eines neuen Strukturtyps sein. Das neue Tag wird in dem Bereich deklariert, in dem es angezeigt wird. In Ihrem speziellen Beispiel ist es ein Dateibereich (da die C-Sprache keinen "Klassenbereich" hat, wie es in C ++ wäre).

Das interessantere Beispiel für dieses Verhalten ist, wenn die Deklaration im Funktionsprototyp erscheint:

%Vor%

In diesem Fall hat die neue Deklaration struct X Funktionsprototyp , die am Ende des Prototyps endet. Wenn Sie später einen Dateibereich struct X deklarieren

%Vor%

und versuchen, einen Zeiger von struct X type an die obige Funktion zu übergeben, gibt der Compiler eine Diagnose über nicht übereinstimmenden Zeigertyp

%Vor%

Dies bedeutet auch sofort, dass in den folgenden Deklarationen

%Vor%

Jede struct X -Deklaration ist eine Deklaration eines anderen -Typs, jeder lokal für seinen eigenen Funktionsprototypbereich.

Aber wenn Sie struct X wie in

deklarieren %Vor%

Alle struct X Referenzen in allen Funktionsprototypen beziehen sich auf die gleiche vorher deklarierte struct X Art.

    
AnT 24.05.2010 07:16
quelle
0

Ich habe mich auch darüber gewundert. Es stellt sich heraus, dass struct NOTHING_LIKE_xyz * z vorwärts deklariert struct NOTHING_LIKE_xyz ist. Als ein verschachteltes Beispiel,

%Vor%

Hier bezieht sich f->bar auf den Typ struct foo , nicht typedef struct { ... } foo . Die erste Zeile wird in Ordnung kompiliert, aber die zweite liefert einen Fehler. Nicht viel für eine Implementierung einer verknüpften Liste dann.

    
Scott Wales 24.05.2010 06:56
quelle
0

Wenn eine Variable oder ein Feld eines Strukturtyps deklariert ist, muss der Compiler genügend Bytes zuweisen, um diese Struktur zu halten. Da die Struktur möglicherweise ein Byte benötigt oder Tausende benötigt, gibt es keine Möglichkeit für den Compiler, zu wissen, wie viel Speicherplatz benötigt wird. Einige Sprachen verwenden Multi-Pass-Compiler, die in der Lage wären, die Größe der Struktur in einem Durchgang herauszufinden und den Platz dafür in einem späteren Durchgang zuzuordnen; Da C für Single-Pass-Compilierung ausgelegt wurde, ist dies jedoch nicht möglich. C verbietet somit die Deklaration von Variablen oder Feldern unvollständiger Strukturtypen.

Andererseits, wenn eine Variable oder ein Feld eines Pointer-to-Structure-Typs deklariert ist, muss der Compiler genügend Bytes zuweisen, um einen Zeiger auf die Struktur zu halten. Unabhängig davon, ob die Struktur ein Byte oder eine Million benötigt, benötigt der Zeiger immer die gleiche Menge an Speicherplatz. Effektiv kann der Compiler den Zeiger auf den unvollständigen Typ als void * setzen, bis er mehr erreicht Informationen über seinen Typ und behandeln sie dann als Zeiger auf den entsprechenden Typ, sobald er mehr darüber erfährt. Der Zeiger vom unvollständigen Typ ist nicht ganz analog zu void *, da man mit void * Dinge tun kann, die man mit unvollständigen Typen nicht tun kann (zB wenn p1 ein Zeiger auf struct s1 ist und p2 ein Zeiger auf struct ist) s2, man kann p1 nicht p2 zuweisen), aber man kann mit einem Zeiger auf einen unvollständigen Typ nichts machen, was man nicht tun könnte, um * ungültig zu machen. Aus der Perspektive des Compilers ist ein Zeiger auf einen unvollständigen Typ im Grunde genommen ein zeigergroßes Byte-Blob. Es kann zu oder von anderen ähnlichen zeigergroßen Blobs von Bytes kopiert werden, aber das ist es. Der Compiler kann Code generieren, um dies zu tun, ohne zu wissen, was sonst noch mit den zeigergroßen Blobs von Bytes geschehen wird.

    
supercat 21.11.2011 00:40
quelle