Считает ли стандарт C, что в этом заголовке есть один или два типа struct uperms_entry?

Можете ли вы привести главу и стих из одного из трех стандартов C (предпочтительно C99 или C11), который указывает, имеет ли следующий заголовочный файл один или дваstruct uperms_entry типа в нем?

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

Дополнительные вопросы:

If there are two types, is there any way to get GCC to report the issue? If there are two types, does it ever matter in practice?

(Я думаю, что ответы «да» # строго; есть два типа », а затем (1) Нет и (2) Нет.)

Context: internal code review — I'd like the order of the structures reversed, but I'm not sure whether I'm being completely overly pedantic.

Update:

Очевидно, что ответ на первоначальный вопрос таков:struct uperms_entry& APOS; и поэтому вопросы с номерами 1 и 2 являются спорными. Я рад, что проверил, прежде чем бросить шипение в обзоре кода.

Background thinking

Этот раздел был добавлен после того, как основной вопрос был решен.

Вот некоторые обширные, но актуальные цитаты из ИСО / МЭК 9899: 2011:

§6.2.7 Compatible type and composite type

¶1 Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators.55) Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name. For two structures, corresponding members shall be declared in the same order. For two structures or unions, corresponding bit-fields shall have the same widths. For two enumerations, corresponding members shall have the same values.

55) Two types need not be identical to be compatible.

§6.7.2.1 Structure and union specifiers

¶8 The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit. The struct-declaration-list is a sequence of declarations for the members of the structure or union. If the struct-declaration-list does not contain any named members, either directly or via an anonymous structure or anonymous union, the behavior is undefined. The type is incomplete until immediately after the } that terminates the list, and complete thereafter.

§6.7.2.3 Tags

¶4 All declarations of structure, union, or enumerated types that have the same scope and use the same tag declare the same type. Irrespective of whether there is a tag or what other declarations of the type are in the same translation unit, the type is incomplete129) until immediately after the closing brace of the list defining the content, and complete thereafter.

¶5 Two declarations of structure, union, or enumerated types which are in different scopes or use different tags declare distinct types. Each declaration of a structure, union, or enumerated type which does not include a tag declares a distinct type.

¶6 A type specifier of the form

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

or

enum identifieropt { enumerator-list }

or

enum identifieropt { enumerator-list , }

declares a structure, union, or enumerated type. The list defines the structure content, union content, or enumeration content. If an identifier is provided,130) the type specifier also declares the identifier to be the tag of that type.

¶7 A declaration of the form

struct-or-union identifier ;

specifies a structure or union type and declares the identifier as a tag of that type.131)

¶8 If a type specifier of the form

struct-or-union identifier

occurs other than as part of one of the above forms, and no other declaration of the identifier as a tag is visible, then it declares an incomplete structure or union type, and declares the identifier as the tag of that type.131)

¶9 If a type specifier of the form

struct-or-union identifier

or

enum identifier

occurs other than as part of one of the above forms, and a declaration of the identifier as a tag is visible, then it specifies the same type as that other declaration, and does not redeclare the tag.

¶12 EXAMPLE 2 To illustrate the use of prior declaration of a tag to specify a pair of mutually referential structures, the declarations

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

specify a pair of structures that contain pointers to each other. Note, however, that if s2 were already declared as a tag in an enclosing scope, the declaration D1 would refer to it, not to the tag s2 declared in D2. To eliminate this context sensitivity, the declaration

struct s2;

may be inserted ahead of D1. This declares a new tag s2 in the inner scope; the declaration D2 then completes the specification of the new type.

129) An incomplete type may only by used when the size of an object of that type is not needed. It is not needed, for example, when a typedef name is declared to be a specifier for a structure or union, or when a pointer to or a function returning a structure or union is being declared. (See incomplete types in 6.2.5.) The specification has to be complete before such a function is called or defined.

130) If there is no identifier, the type can, within the translation unit, only be referred to by the declaration of which it is a part. Of course, when the declaration is of a typedef name, subsequent declarations can make use of that typedef name to declare objects having the specified structure, union, or enumerated type.

131) A similar construction with enum does not exist.

§6.7.3 Type qualifiers

¶10 For two qualified types to be compatible, both shall have the identically qualified version of a compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the specified type.

The discussion in §6.7.6 is related to pointer, arrays, and function declarators and does not really affect structures or unions.

Я знал о Примере 2, когда писал вопрос. Это какой-то размышлять вслух о том, что означает приведенная выше информация.

Рассмотрим этот пример, который аккуратно компилируется:

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

Функция иллюстрирует, когда применяется Пример 2, но не имеет смысла код. Декларацияp1 в функции это будет структура того же типа, что и глобальная переменнаяp0, Хотя его имя типа являетсяstruct r1это другой (и несовместимый) тип тип локальной переменнойp.

Переопределениеstruct r1 на глобальном уровне не допускается, независимо от того, назван ли элементx или жеy, До struct r1; это неоперация в этом контексте.

Один интересный вопрос - «может функционировать»z проходитьp или жеq к любому другому функция (назовите этоa)? Ответ квалифицированный "да", и некоторые из ограничения интересны. (Это также было бы ужасное кодирование стиль, чтобы попробовать это, граничащее с безумным.) Функция должна существовать в отдельный переводческий блок (ТУ). Объявление функции должно быть внутри функцияz (потому что если он находится вне функции, его прототип должен обратитесь кstruct r1 определяется вне функции, а неstruct r1 определяется внутри.

В другом TU, степень здравомыслия должна преобладать: функцияa должен иметь совместимые типы структурstruct r1 а такжеstruct r2 видимый в глобальном масштабе.

Вот еще один пример, но этот не компилируется:

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

Предупреждения из GCC 4.7.1 в Mac OS X 10.7.4:

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]

Строки 13 - это заданиеp.rn = &q; в функцииy и строка 23 попытка определить и инициализироватьstruct r2 p в функцииz.

Это показывает, что в рамках функцийrn элементstruct r2 указатель на неполный типstruct r1 объявлено на глобальный охват. Добавлениеstruct r1; как первая строка кода внутри функция позволит скомпилировать код, но инициализация привязкаr1p->rn разыменовывает указатель на неполный тип опять же (неполный типstruct r1 объявлен в глобальном объем).

Объявления функций и предыдущиеstruct r1; линия могла появляются в заголовке как непрозрачный тип. Список вспомогательных функций неполный; должен быть способ получить указатель на инициализируетсяstruct r1 перейти к функциям, но это деталь.

Чтобы код работал в этом втором TU, типы дляstruct r1 должен быть завершенным в глобальной области видимости до определения функций, и из-за рекурсивных ссылок `struct r21 также должна быть завершена.

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

Этот процесс определения структур в файле реализации в то время как оставив тип незавершенным в общедоступном заголовочном файле, можно повторить несколько файлов реализации, если необходимо, хотя, если более одного TU использует полное определение структуры, было бы лучше разместить определения в частном заголовочном файле, разделяемом только между файлами, которые реализовать структуры. Я отмечаю, что не имеет значения, является ли закрытый заголовок предшествует или следует за публичным заголовком.

Может быть, это все уже было очевидно для вас. Мне не нужно было думать об этом до этого уровня детализации раньше.

Ответы на вопрос(2)

Ваш ответ на вопрос