Czy standard C uważa, że ​​w tym nagłówku istnieje jeden lub dwa typy „struct uperms_entry”?

Czy możesz podać rozdział i werset z jednego z trzech standardów C (najlepiej C99 lub C11), który wskazuje, czy następujący plik nagłówkowy ma jeden czy dwastruct uperms_entry typy w nim?

#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 */

Pytania pomocnicze:

Jeśli istnieją dwa typy, czy jest jakiś sposób, aby GCC zgłosił problem?Jeśli istnieją dwa typy, czy ma to znaczenie w praktyce?

(Myślę, że odpowiedzi brzmią „tak - ściśle istnieją dwa typy”, a następnie (1) Nie i (2) Nie).

Kontekst: przegląd kodu wewnętrznego - chciałbym, aby kolejność struktur została odwrócona, ale nie jestem pewien, czy jestem całkowicie przesadnie pedantyczny.

Aktualizacja:

Oczywiście odpowiedź na pierwsze pytanie brzmi: „jest jednastruct uperms_entry„i dlatego pytania numerowane 1 i 2 są dyskusyjne. Cieszę się, że sprawdziłem, zanim rzuciłem hissy fit w przeglądzie kodu.

Myślenie w tle

Ta sekcja została dodana długo po rozwiązaniu podstawowego pytania.

Oto kilka obszernych, ale istotnych cytatów z ISO / IEC 9899: 2011:

§6.7.7 Typ zgodny i typ złożony

¶ 1 Dwa typy mają zgodny typ, jeśli ich typy są takie same. Dodatkowe zasady określania, czy dwa typy są zgodne, opisano w 6.7.2 dla specyfikatorów typów, w 6.7.3 dla kwalifikatorów typu oraz w 6.7.6 dla deklaratorów.55) Ponadto dwa typy struktur, związków lub wyliczone zadeklarowane w oddzielnych jednostkach translacji są zgodne, jeśli ich znaczniki i elementy spełniają następujące wymagania: Jeśli jeden jest zadeklarowany ze znacznikiem, drugi jest zadeklarowany przy użyciu tego samego znacznika. Jeśli oba są wypełnione w dowolnym miejscu w obrębie odpowiednich jednostek tłumaczeniowych, zastosowanie mają następujące dodatkowe wymagania: między ich członkami powinna istnieć korespondencja jeden-do-jednego, tak aby każda para odpowiednich członków była deklarowana z kompatybilnymi typami; jeśli jeden członek pary jest zadeklarowany ze specyfikatorem wyrównania, drugi jest zadeklarowany z równoważnym specyfikatorem wyrównania; a jeśli jeden członek pary jest zadeklarowany z nazwą, drugi jest zadeklarowany z tą samą nazwą. W przypadku dwóch struktur odpowiadających członków należy zadeklarować w tej samej kolejności. Dla dwóch struktur lub związków odpowiednie pola bitowe mają takie same szerokości. W przypadku dwóch wyliczeń odpowiadający im członkowie mają takie same wartości.

55) Dwa typy nie muszą być identyczne, aby były kompatybilne.

§6.7.2.1 Struktura i specyfikatory związków

¶8 Obecność listy-deklaracji struktury w specyfikatorze struktury lub unii deklaruje nowy typ w jednostce translacji. Lista deklaracji struktury to sekwencja deklaracji dla członków struktury lub związku. Jeśli lista deklaracji struktury nie zawiera żadnych nazwanych członków, bezpośrednio lub za pośrednictwem anonimowej struktury lub anonimowej unii, zachowanie jest niezdefiniowane. Typ jest niekompletny do chwili tuż po} który kończy listę i kończy się później.

§6.7.2.3 Tagi

¶4 Wszystkie deklaracje typów struktur, związków lub wyliczonych, które mają ten sam zasięg i używają tego samego znacznika, deklarują ten sam typ. Niezależnie od tego, czy istnieje tag lub jakie inne deklaracje typu znajdują się w tej samej jednostce tłumaczeniowej, typ jest niekompletny129) aż do natychmiastowego zamknięcia nawiasu na liście definiującej treść i uzupełnionej później.

¶5 Dwie deklaracje typów struktur, związków lub wyliczonych, które są w różnych zakresach lub używają różnych znaczników, deklarują różne typy. Każda deklaracja struktury, unii lub typu wyliczeniowego, która nie zawiera znacznika, deklaruje odrębny typ.

¶6 Specyfikator typu formularza

struct-or-union identifieroptować { struct-declaration-list }

lub

enum identifieroptować { enumerator-list }

lub

enum identifieroptować { enumerator-list , }

deklaruje typ struktury, unii lub wyliczenia. Lista określa zawartość struktury, zawartość związku lub zawartość wyliczenia. Jeśli podany jest identyfikator,130) specyfikator typu deklaruje również, że identyfikator jest znacznikiem tego typu.

¶7 Deklaracja formularza

struct-or-union identifier ;

określa strukturę lub typ unii i deklaruje identyfikator jako tag tego typu.131)

¶8 Jeśli specyfikator typu formularza

struct-or-union identifier

występuje inaczej niż jako część jednej z powyższych form, a żadna inna deklaracja identyfikatora jako znacznika nie jest widoczna, następnie deklaruje niekompletną strukturę lub typ unii i deklaruje identyfikator jako znacznik tego typu.131)

¶ 9 Jeśli specyfikator typu formularza

struct-or-union identifier

lub

enum identifier

występuje inaczej niż jako część jednej z powyższych form, a deklaracja identyfikatora jako znacznika jest widoczna, a następnie określa ten sam typ co ta inna deklaracja i nie zmienia oznaczenia znacznika.

¶ 12 PRZYKŁAD 2 Aby zilustrować użycie wcześniejszej deklaracji znacznika do określenia pary wzajemnie odwoływanych struktur, deklaracje

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

określ parę struktur, które zawierają wskaźniki do siebie. Zauważ jednak, że jeśli s2 zostało już zadeklarowane jako znacznik w zakresie obejmującym, deklaracja D1 odwoła się do niego, a nie do znacznika s2 zadeklarowanego w D2. Aby wyeliminować tę wrażliwość na kontekst, deklaracja

struct s2;

może być wstawiony przed D1. Deklaruje nowy tag s2 w wewnętrznym zakresie; deklaracja D2 uzupełnia specyfikację nowego typu.

129) Typ niekompletny może być użyty tylko wtedy, gdy rozmiar obiektu tego typu nie jest potrzebny. Nie jest to potrzebne, na przykład, gdy nazwa typedef jest zadeklarowana jako specyfikator struktury lub unii, lub gdy deklarowany jest wskaźnik lub funkcja zwracająca strukturę lub związek. (Patrz niekompletne typy w 6.2.5.) Specyfikacja musi być kompletna, zanim taka funkcja zostanie wywołana lub zdefiniowana.

130) Jeśli nie ma identyfikatora, typ może w jednostce tłumaczeniowej odwoływać się tylko do deklaracji, której jest częścią. Oczywiście, gdy deklaracja ma nazwę typedef, kolejne deklaracje mogą używać tej nazwy typedef do deklarowania obiektów o określonej strukturze, unii lub typie wyliczeniowym.

131) Podobna konstrukcja z enum nie istnieje.

§6.7.3 Kwalifikatory typu

¶10 Aby dwa typy kwalifikowane były kompatybilne, oba powinny mieć identyczną wersję zgodnego typu; kolejność kwalifikatorów typów na liście specyfikatorów lub kwalifikatorów nie wpływa na określony typ.

Dyskusja w §6.6.6 odnosi się do wskaźników, tablic i deklaratorów funkcji i tak naprawdę nie wpływa na struktury ani związki.

Byłem świadomy przykładu 2, kiedy pisałem pytanie. To jest trochę myślenia na temat niektórych z powyższych informacji.

Rozważmy ten przykład, który czysto kompiluje:

#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);
}

Funkcja ilustruje, kiedy ma zastosowanie Przykład 2, ale nie jest kodem sensownym. Deklaracjap1 w funkcji jest to struktura tego samego typu co zmienna globalnap0. Nawet jeśli jego nazwa tostruct r1, jest innego (i niekompatybilnego) typu niż typ zmiennej lokalnejp.

Przedefiniowaniestruct r1 na poziomie globalnym jest niedozwolone, niezależnie od tego, czy element ma nazwęx luby. Przeorstruct r1; w tym kontekście jest no-op.

Jedną z interesujących kwestii jest „może działaćz przechodzićp lubq do dowolnej innej funkcji (nazwij toa)? Odpowiedź brzmi: „tak”, a niektóre ograniczenia są interesujące. (Próbowanie tego byłoby również przerażającym stylem kodowania, graniczącym z obłąkaniem.) Funkcja musi istnieć w oddzielnej jednostce tłumaczeniowej (TU). Deklaracja funkcji musi znajdować się wewnątrz funkcjiz (ponieważ jeśli znajduje się poza funkcją, jej prototyp musi odnosić się dostruct r1 zdefiniowane poza funkcją, a niestruct r1 zdefiniowane wewnątrz.

W drugiej JT musi przeważać pewien stopień rozsądku: funkcjaa musi mieć kompatybilne typy strukturstruct r1 istruct r2 widoczne w jej globalnym zakresie.

Oto kolejny przykład, ale ten nie kompiluje:

#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);
}

Ostrzeżenia z GCC 4.7.1 w systemie Mac OS X 10.7.4 to:

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]

Linie 13 to zadaniep.rn = &q; w działaniuy a linia 23 jest próbą zdefiniowania i zainicjowaniastruct r2 p w działaniuz.

To pokazuje, że w ramach funkcji,rn elementstruct r2 jest wskaźnikiem do niekompletnego typustruct r1 zadeklarowany w skali globalnej. Dodawanie astruct r1; ponieważ pierwsza linia kodu wewnątrz funkcji pozwoliłaby na kompilację kodu, ale odwołanie do inicjalizacjir1p->rn derefencing wskaźnik do niekompletnego typu ponownie (niekompletny typ tostruct r1 zadeklarowany w zakresie globalnym).

Deklaracje funkcji i poprzedniestruct r1; linia może pojawić się w nagłówku jako nieprzezroczysty. Lista funkcji pomocniczych jest niekompletna; musiałby istnieć sposób na uzyskanie wskaźnika do zainicjowanegostruct r1 przejść do funkcji, ale to jest szczegół.

Aby kod działał w tej drugiej jednostce tłumaczeniowej, typy dlastruct r1 musi być kompletny w zakresie globalnym przed zdefiniowaniem funkcji, a ze względu na odwołania rekurencyjne, `struct r21 musi być także kompletny.

#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);
}

Ten proces definiowania struktur w pliku implementacyjnym, pozostawiając typ niekompletny w publicznym pliku nagłówkowym, można powtórzyć w wielu plikach implementacyjnych, jeśli to konieczne, chociaż jeśli więcej niż jedna jednostka TU używa pełnej definicji struktury, lepiej byłoby umieścić definicje w prywatnym pliku nagłówkowym współdzielonym tylko między plikami implementującymi struktury. Zaznaczam, że nie ma znaczenia, czy prywatny nagłówek poprzedza, czy podąża za nagłówkiem publicznym.

Może to już było dla ciebie oczywiste. Nie musiałem wcześniej zastanawiać się nad tym szczegółem.

questionAnswers(2)

yourAnswerToTheQuestion