Berücksichtigt der C-Standard, dass dieser Header einen oder zwei 'struct uperms_entry'-Typen enthält?

Können Sie ein Kapitel und einen Vers aus einem der drei C-Standards (vorzugsweise C99 oder C11) angeben, der angibt, ob die folgende Header-Datei einen oder zwei enthält?struct uperms_entry tippt es ein?

#ifndef UPERMS_CACHE_INCLUDE
#define UPERMS_CACHE_INCLUDE

typedef struct mutex MT_MUTEX;

typedef struct uperms_cache
{
    MT_MUTEX            *cache_lock;
    int                  processing;
    struct uperms_entry *uperms_list;  // No prior struct uperms_entry
} uperms_cache_t;

typedef struct uperms_entry // Does this define a different struct uperms_entry?
{
    char                 username[32];
    int                  perms;
    struct uperms_entry *next;
} uperms_entry_t;

#endif /* UPERMS_CACHE_INCLUDE */

Ergänzende Fragen:

Wenn es zwei Arten gibt, gibt es eine Möglichkeit, GCC dazu zu bringen, das Problem zu melden?Wenn es zwei Arten gibt, spielt es in der Praxis überhaupt eine Rolle?

(Ich denke, die Antworten lauten "Ja - streng genommen gibt es zwei Arten" und dann (1) Nein und (2) Nein.)

Kontext: Interne Codeüberprüfung - Ich möchte, dass die Reihenfolge der Strukturen umgekehrt wird, bin mir aber nicht sicher, ob ich völlig übermäßig pedantisch bin.

Aktualisieren:

Die Antwort auf die ursprüngliche Frage lautet eindeutig: "Es gibt eine."struct uperms_entry'und deshalb sind die Fragen 1 und 2 strittig. Ich bin froh, dass ich das überprüft habe, bevor ich eine Codeüberprüfung durchgeführt habe.

Hintergrund denken

Dieser Abschnitt wurde hinzugefügt, lange nachdem die primäre Frage gelöst wurde.

Hier sind einige umfangreiche, aber relevante Zitate aus ISO / IEC 9899: 2011:

§6.2.7 Kompatibler Typ und zusammengesetzter Typ

¶1 Zwei Typen haben einen kompatiblen Typ, wenn ihre Typen identisch sind. Zusätzliche Regeln zur Feststellung, ob zwei Typen kompatibel sind, sind in 6.7.2 für Typspezifizierer, in 6.7.3 für Typqualifizierer und in 6.7.6 für Deklaratoren beschrieben.55) Darüber hinaus sind zwei in separaten Übersetzungseinheiten deklarierte Struktur-, Vereinigungs- oder Aufzählungstypen kompatibel, wenn ihre Tags und Member die folgenden Anforderungen erfüllen: Wenn einer mit einem Tag deklariert wird, muss der andere mit demselben Tag deklariert werden. Werden beide an einer beliebigen Stelle in ihren jeweiligen Übersetzungseinheiten ausgefüllt, gelten die folgenden zusätzlichen Anforderungen: Zwischen ihren Mitgliedern besteht eine Eins-zu-Eins-Korrespondenz, sodass jedes Paar korrespondierender Mitglieder mit kompatiblen Typen deklariert wird; Wenn ein Mitglied des Paares mit einem Ausrichtungsspezifizierer deklariert wird, wird das andere mit einem äquivalenten Ausrichtungsspezifizierer deklariert. und wenn ein Mitglied des Paares mit einem Namen deklariert wird, wird das andere mit demselben Namen deklariert. Bei zwei Strukturen sind die entsprechenden Mitglieder in derselben Reihenfolge zu erklären. Bei zwei Strukturen oder Vereinigungen müssen die entsprechenden Bitfelder die gleiche Breite haben. Bei zwei Aufzählungen haben die entsprechenden Mitglieder dieselben Werte.

55) Zwei Typen müssen nicht identisch sein, um kompatibel zu sein.

§6.7.2.1 Struktur- und Vereinigungsspezifizierer

¶8 Das Vorhandensein einer Strukturdeklarationsliste in einem Struktur- oder Vereinigungsspezifizierer deklariert einen neuen Typ innerhalb einer Übersetzungseinheit. Die Strukturdeklarationsliste ist eine Folge von Deklarationen für die Mitglieder der Struktur oder Union. Wenn die Strukturdeklarationsliste keine benannten Elemente enthält, weder direkt noch über eine anonyme Struktur oder anonyme Vereinigung, ist das Verhalten undefiniert. Der Typ ist bis unmittelbar nach dem unvollständig} das beendet die Liste und vervollständigt danach.

§6.7.2.3 Tags

¶4 Alle Deklarationen von Struktur-, Vereinigungs- oder Aufzählungstypen, die denselben Gültigkeitsbereich haben und denselben Tag verwenden, deklarieren denselben Typ. Unabhängig davon, ob ein Tag vorhanden ist oder welche anderen Deklarationen des Typs sich in derselben Übersetzungseinheit befinden, ist der Typ unvollständig129) bis unmittelbar nach der schließenden Klammer der Liste, die den Inhalt definiert, und danach vervollständigen.

¶5 Zwei Deklarationen von Struktur-, Vereinigungs- oder Aufzählungstypen, die sich in unterschiedlichen Bereichen befinden oder unterschiedliche Tags verwenden, deklarieren unterschiedliche Typen. Jede Deklaration einer Struktur, Union oder eines Aufzählungstyps, die kein Tag enthält, deklariert einen eigenen Typ.

¶6 Ein Typbezeichner des Formulars

struct-or-union identifieropt { struct-declaration-list }

oder

enum identifieropt { enumerator-list }

oder

enum identifieropt { enumerator-list , }

deklariert eine Struktur, eine Vereinigung oder einen Aufzählungstyp. Die Liste definiert den Strukturinhalt, den Unionsinhalt oder den Aufzählungsinhalt. Wenn eine Kennung angegeben ist,130) Der Typbezeichner deklariert den Bezeichner auch als das Tag dieses Typs.

¶7 Eine Erklärung des Formulars

struct-or-union identifier ;

Gibt eine Struktur oder einen Unionstyp an und deklariert den Bezeichner als Tag dieses Typs.131)

¶8 Wenn ein Typbezeichner des Formulars

struct-or-union identifier

tritt anders als als als Teil eines der obigen Formulare auf und keine andere Deklaration des Bezeichners als Tag ist sichtbar, dann deklariert es eine unvollständige Struktur oder einen unionsartigen Typ und deklariert den Bezeichner als das Tag dieses Typs.131)

¶9 Wenn ein Typbezeichner des Formulars

struct-or-union identifier

oder

enum identifier

tritt anders als als als Teil eines der obigen Formulare auf und eine Deklaration des Bezeichners als Tag ist sichtbar, dann gibt es den gleichen Typ wie die andere Deklaration an und deklariert das Tag nicht neu.

¶12 BEISPIEL 2 Zur Veranschaulichung der Verwendung der vorherigen Deklaration eines Tags zur Angabe eines Paares von gegenseitig referenzierenden Strukturen, den Deklarationen

struct s1 { struct s2 *s2p; /* ... */ }; // D1
struct s2 { struct s1 *s1p; /* ... */ }; // D2

Geben Sie ein Paar von Strukturen an, die Zeiger aufeinander enthalten. Beachten Sie jedoch, dass sich die Deklaration D1 auf s2 bezieht, wenn s2 bereits als Tag in einem umschließenden Bereich deklariert wurde, und nicht auf das in D2 deklarierte Tag s2. Um diese Kontextsensitivität zu beseitigen, wird die Deklaration

struct s2;

darf vor D1 eingefügt werden. Dies deklariert ein neues Tag s2 im inneren Bereich; Die Deklaration D2 vervollständigt dann die Spezifikation des neuen Typs.

129) Ein unvollständiger Typ kann nur verwendet werden, wenn die Größe eines Objekts dieses Typs nicht benötigt wird. Dies ist beispielsweise nicht erforderlich, wenn ein typedef-Name als Bezeichner für eine Struktur oder Union deklariert wird oder wenn ein Zeiger auf oder eine Funktion deklariert wird, die eine Struktur oder Union zurückgibt. (Siehe unvollständige Typen in 6.2.5.) Die Spezifikation muss vollständig sein, bevor eine solche Funktion aufgerufen oder definiert wird.

130) Wenn keine Kennung vorhanden ist, kann der Typ innerhalb der Übersetzungseinheit nur durch die Deklaration angegeben werden, zu der er gehört. Wenn die Deklaration einen Typedef-Namen hat, können nachfolgende Deklarationen diesen Typedef-Namen verwenden, um Objekte mit der angegebenen Struktur, Union oder dem angegebenen Aufzählungstyp zu deklarieren.

131) Eine ähnliche Konstruktion mit enum gibt es nicht.

§6.7.3 Typqualifizierer

¶10 Damit zwei qualifizierte Typen kompatibel sind, müssen beide die identische qualifizierte Version eines kompatiblen Typs haben. Die Reihenfolge der Typqualifizierer in einer Liste von Spezifizierern oder Qualifizierern wirkt sich nicht auf den angegebenen Typ aus.

Die Erörterung in §6.7.6 bezieht sich auf Zeiger, Arrays und Funktionsdeklaratoren und wirkt sich nicht wirklich auf Strukturen oder Vereinigungen aus.

Beispiel 2 war mir bekannt, als ich die Frage schrieb. Dies ist ein lautes Nachdenken über einige der oben genannten Informationen.

Betrachten Sie dieses Beispiel, das sauber kompiliert:

#include <stdio.h>
struct r1 { int x; };

struct r1;

struct r1 p0;

//struct r1 { int y; };     // Redefinition of struct r1

extern void z(void);

void z(void)
{
    struct r1 p1 = { 23 };
    struct r1;
    //struct r1 p2;         // Storage size of p2 is not known
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { 0, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
    printf("p1.x = %d\n", p1.x);
}

Die Funktion zeigt, wenn Beispiel 2 zutrifft, es sich jedoch nicht um sinnvollen Code handelt. Die Erklärung vonp1 In der Funktion wäre es eine Struktur des gleichen Typs wie die globale Variablep0. Obwohl sein Typname iststruct r1, ist es von einem anderen (und nicht kompatiblen) Typ als der Typ der lokalen Variablenp.

Die Neudefinition vonstruct r1 auf globaler Ebene ist nicht zulässig, unabhängig davon, ob das Element benannt istx odery. Die vorherigestruct r1; ist in diesem Zusammenhang ein No-Op.

Ein interessantes Problem ist, dass es funktionieren kannz bestehenp oderq zu einer anderen Funktion (nennen Sie esa)? Die Antwort ist ein qualifiziertes "Ja", und einige der Einschränkungen sind interessant. (Es wäre auch ein entsetzlicher Codierungsstil, es zu versuchen, der dem Wahnsinn nahe kommt.) Die Funktion muss in einer separaten Übersetzungseinheit (TU) vorhanden sein. Die Funktionsdeklaration muss innerhalb der Funktion seinz (Wenn es sich außerhalb der Funktion befindet, muss sich der Prototyp auf diestruct r1 außerhalb der Funktion definiert, nicht diestruct r1 innen definiert.

In der anderen TU muss ein gewisses Maß an Vernunft vorherrschen: die Funktiona muss die kompatiblen Strukturtypen habenstruct r1 undstruct r2 in seinem globalen Geltungsbereich sichtbar.

Hier ist ein weiteres Beispiel, das jedoch nicht kompiliert wird:

#include <stdio.h>

struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);

void y(struct r1 *r1p)
{
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { r1p, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

void z(struct r1 *r1p)
{
    struct r1
    struct r2 { struct r1 *rn; int y; };
    struct r1 { struct r2 *rn; int z; };
    struct r2 p = { r1p, 1 };
    struct r1 q = { &p, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

Die Warnungen von GCC 4.7.1 unter Mac OS X 10.7.4 lauten:

structs3.c: In function 'y':
structs3.c:13:10: warning: assignment from incompatible pointer type [enabled by default]
structs3.c: In function 'z':
structs3.c:22:12: warning: initialization from incompatible pointer type [enabled by default]
structs3.c:22:12: warning: (near initialization for 'p.rn') [enabled by default]

Zeile 13 ist die Zuordnungp.rn = &q; in Funktiony und Zeile 23 ist der Versuch zu definieren und zu initialisierenstruct r2 p in Funktionz.

Dies zeigt, dass innerhalb der Funktionen diern Element vonstruct r2 ist ein Zeiger auf den unvollständigen Typstruct r1 im globalen Geltungsbereich deklariert. Hinzufügen einesstruct r1; als erste Codezeile innerhalb der Funktion würde der Code kompiliert, aber die Initialisierung referenziertr1p->rn Dereferenziert einen Zeiger erneut auf einen unvollständigen Typ (der unvollständige Typ ist derstruct r1 global deklariert).

Die Funktionsdeklarationen und die vorhergehendenstruct r1; Die Zeile könnte in einer Kopfzeile als undurchsichtiger Typ erscheinen. Die Liste der unterstützenden Funktionen ist unvollständig. Es müsste eine Möglichkeit geben, einen Zeiger auf ein initialisiertes Objekt zu erhaltenstruct r1 in die Funktionen übergehen, aber das ist ein Detail.

Damit der Code in dieser zweiten TE funktioniert, müssen die Typen fürstruct r1 muss im globalen Gültigkeitsbereich vollständig sein, bevor die Funktionen definiert werden, und wegen der rekursiven Referenzen muss auch `struct r21 vollständig sein.

#include <stdio.h>

/* Logically in a 3-line header file */
struct r1;
extern void z(struct r1 *r1p);
extern void y(struct r1 *r1p);

/* Details private to this TU */
struct r2 { struct r1 *rn; int y; };
struct r1 { struct r2 *rn; int z; };

void y(struct r1 *r1p)
{
    struct r2 p = { r1p,     1 };
    struct r1 q = { r1p->rn, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

void z(struct r1 *r1p)
{
    struct r2 p = { r1p,     1 };
    struct r1 q = { r1p->rn, 2 };
    p.rn = &q;
    printf("p.y = %d, q.z = %d\n", p.y, q.z);
}

Dieser Prozess des Definierens der Strukturen in der Implementierungsdatei, während der Typ in der öffentlichen Headerdatei unvollständig bleibt, kann bei Bedarf in mehreren Implementierungsdateien wiederholt werden. Wenn jedoch mehr als eine TU die vollständige Strukturdefinition verwendet, ist es besser, die Definitionen zu platzieren in einer privaten Headerdatei, die nur von den Dateien verwendet wird, die die Strukturen implementieren. Ich stelle fest, dass es keine Rolle spielt, ob der private Header dem öffentlichen Header vorausgeht oder diesem folgt.

Vielleicht war dir das alles schon klar. Ich musste es mir noch nie so genau überlegen.

Antworten auf die Frage(2)

Ihre Antwort auf die Frage