C标准是否认为此标头中有一个或两个'struct uperms_entry'类型?

Jon*_*ler 4 c c99

您能否从三个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 */
Run Code Online (Sandbox Code Playgroud)

附带问题:

  1. 如果有两种类型,有没有办法让GCC报告问题?
  2. 如果有两种类型,它在实践中是否重要?

(我认为答案是'是 - 严格来说有两种类型',然后(1)否和(2)否.)

上下文:内部代码审查 - 我希望结构的顺序颠倒过来,但我不确定我是否完全过于迂腐.

更新:

显然,最初问题的答案是"有一个struct uperms_entry",因此编号为1和2的问题没有实际意义.我很高兴我在检查代码之前检查了一下.

背景思考

主要问题解决后很久就添加了此部分.


以下是ISO/IEC 9899:2011中广泛但相关的引用:

§6.2.7兼容型和复合型

1如果类型相同,则两种类型具有兼容类型.用于确定两种类型是否兼容的附加规则在6.7.2中描述了类型说明符,在6.7.3中描述了类型限定符,在6.7.6中描述了声明符.55)此外,如果它们的标签和成员满足以下要求,则在单独的翻译单元中声明的两个结构,联合或枚举类型是兼容的:如果使用标签声明一个,则另一个应使用相同的标签声明.如果两者都在其各自的翻译单元内的任何地方完成,则以下附加要求适用:其成员之间应存在一对一的对应关系,以便每对相应的成员被宣布为兼容类型; 如果使用对齐说明符声明该对中的一个成员,则使用等效的对齐说明符声明另一个成员; 如果该对的一个成员使用名称声明,则另一个成员使用相同的名称声明.对于两个结构,相应的成员应按相同的顺序声明.对于两个结构或联合,相应的位域应具有相同的宽度.对于两个枚举,相应的成员应具有相同的值.

55)两种类型不必相同即可兼容.

§6.7.2.1结构和联合说明符

8struct-or-union-specifier中struct-declaration-list的存在在转换单元中声明了一个新类型.struct-declaration-list是结构或联合成员的一系列声明.如果struct-declaration-list不包含任何命名成员(直接或通过匿名结构或匿名联合),则行为未定义.该类型在}该列表终止之后立即完成,并在此后完成.

§6.7.2.3标签

4具有相同范围且使用相同标记的结构,联合或枚举类型的所有声明声明相同的类型.无论是否存在标签或该类型的其他声明在同一翻译单元中,该类型都是不完整的129)直到定义内容的列表的右括号之后,并且此后完成.

5结构,联合或枚举类型的两个声明属于不同的范围或使用不同的标记声明了不同的类型.结构,联合或枚举类型的每个声明(不包含标记)都声明了不同的类型.

6表单的类型说明符

struct-or-union identifier选择 { struct-declaration-list }

要么

enum identifier选择 { enumerator-list }

要么

enum identifier选择 { enumerator-list , }

声明结构,联合或枚举类型.该列表定义结构内容,联合内容或枚举内容.如果提供了标识符,则130)类型说明符还将标识符声明为该类型的标记.

7表格的声明

struct-or-union identifier ;
Run Code Online (Sandbox Code Playgroud)

指定结构或联合类型,并将标识符声明为该类型的标记.131)

8如果是表单的类型说明符

struct-or-union identifier
Run Code Online (Sandbox Code Playgroud)

除了作为上述形式之一的一部分之外,不会发生标识符作为标记的其他声明,然后它声明一个不完整的结构或联合类型,并将该标识符声明为该类型的标记.131)

9如果是表单的类型说明符

struct-or-union identifier
Run Code Online (Sandbox Code Playgroud)

要么

enum identifier
Run Code Online (Sandbox Code Playgroud)

除了作为上述形式之一的一部分发生之外,标识符作为标记的声明是可见的,然后它指定与其他声明相同的类型,并且不重新声明标记.

12示例2说明使用标记的先前声明来指定一对相互参照结构的声明

struct s1 { struct s2 *s2p; /* ... */ }; // D1
struct s2 { struct s1 *s1p; /* ... */ }; // D2
Run Code Online (Sandbox Code Playgroud)

指定一对包含彼此指针的结构.但请注意,如果s2已经在封闭范围内声明为标记,则声明D1将引用它,而不是引用D2中声明的标记s2.为了消除这种上下文敏感性,声明

struct s2;
Run Code Online (Sandbox Code Playgroud)

可以在D1之前插入.这在内部范围内声明了一个新标签s2; 声明D2然后完成新类型的规范.

129)仅当不需要该类型的对象的大小时,才可以使用不完整类型.例如,当typedef名称声明为结构或联合的说明符,或者声明指向返回结构或联合的函数的指针时,则不需要它.(参见6.2.5中的不完整类型.)在调用或定义此类函数之前,必须完成规范.

130)如果没有标识符,则翻译单元内的类型只能通过其作为一部分的声明来引用.当然,当声明是typedef名称时,后续声明可以使用该typedef名称来声明具有指定结构,联合或枚举类型的对象.

131)不存在与枚举类似的结构.

§6.7.3类型限定符

10对于兼容的两种合格类型,两者都应具有相同类型的兼容类型; 说明符或限定符列表中类型限定符的顺序不会影响指定的类型.

§6.7.6中的讨论与指针,数组和函数声明符有关,并不会真正影响结构或联合.


当我写这个问题时,我知道例子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);
}
Run Code Online (Sandbox Code Playgroud)

该函数说明了示例2的适用时间,但不是合理的代码.p1函数中的声明将是与全局变量相同类型的结构p0.即使它的类型名称是struct r1,它与本地变量的类型具有不同(且不兼容)的类型p.

重新定义struct r1在全球范围内是不允许的,不管这个元素是否已命名xy.struct r1;在这种情况下,先验 是无操作.

一个有趣的问题是'可以函数z传递p还是q任何其他函数(调用它a)?答案是合格的"是",一些约束很有趣.(尝试它也是令人震惊的编码风格,接近疯狂.)该功能必须存在于单独的翻译单元(TU)中.函数声明必须在函数内部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);
}
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

第13行是p.rn = &q;函数中的赋值,y第23行是struct r2 p在函数中定义和初始化的尝试z.

这表明在函数中,rn元素struct r2是指向struct r1在全局范围内声明的不完整类型的指针.struct r1;在函数内添加第一行代码将允许代码进行编译,但初始化引用r1p->rn再次将指针解除指向不完整类型(不完整类型是struct r1在全局范围内声明的).

函数声明和前struct r1;一行可以作为opaque类型出现在标题中.支持功能清单不完整; 需要有一种方法来获取指向初始化的指针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);
}
Run Code Online (Sandbox Code Playgroud)

如果需要,可以在多个实现文件中重复定义实现文件中的结构而在公共头文件中保留类型不完整的过程,但是如果多个TU使用完整的结构定义,则最好放置定义在专用头文件中仅在实现结构的文件之间共享.我注意到私有头是否在公共头之前或之后并不重要.

也许这对你来说已经很明显了.我之前不需要在这个细节层次上考虑它.

caf*_*caf 6

在C 1,它们指的是相同的类型.C99§6.2.1定义了存在的范围:

2对于标识符指定的每个不同实体,标识符仅在称为其范围的程序文本区域内可见(即,可以使用).由相同标识符指定的不同实体具有不同的范围,或者在不同的名称空间中.范围有四种:函数,文件,块和函数原型.(函数原型是声明其参数类型的函数的声明.)

功能范围仅适用于标签(如同一节中稍后明确说明的).块作用域适用于块中声明的标识符- 块由复合语句,迭代语句和选择语句(而不是struct声明或复合初始化程序)创建.函数原型范围适用于在函数原型声明中声明的标识符.

这些都不适用于您的示例 - 您的示例中的所有提及struct uperms_entry都在文件范围内.

C99§6.7.2.3说:

1具有相同作用域且使用相同标记的结构,联合或枚举类型的所有声明都声明相同的类型.在定义内容的列表的右括号之前,类型是不完整的,之后完成.

这很清楚,适用于您的情况.

该节第8段适用于首次提及struct uperms_entry:

8如果形式为struct-or-union标识符的类型说明不是作为上述表单之一的一部分而发生,并且没有其他标识符作为标记声明可见,则它声明一个不完整的结构或联合类型,并且将标识符声明为该类型的标记.

所以在那时它被声明为文件范围的不完整类型.第6段适用于第二次提及struct uperms_entry:

6形式为struct-or-union标识符的类型说明opt {struct-declaration-list}enum identifier {enumerator-list}enum identifier {enumerator-list,}声明结构,联合或枚举类型.该列表定义结构内容,联合内容或枚举内容.如果提供了标识符,则类型说明符还将标识符声明为该类型的标记.

因此,}在typedef声明结束后,它现在是一个完整的类型.

附属问题没有实际意义.


但是,我认为在C++中并非如此.