C标准是否认为这个头文件中有一个或两个“struct uperms_entry”types?
你可以从三个C标准(最好是C99或C11)中select一个来表示下面的头文件是否有一个或两个struct uperms_entry
types?
#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 */
附加问题:
- 如果有两种types,有没有办法让海湾合作委员会报告这个问题?
- 如果有两种types,在实践中是否重要?
(我认为答案是“是 – 严格有两种types”,然后是(1)否和(2)否)
上下文:内部代码审查 – 我想要颠倒的结构的顺序,但我不知道我是否完全过于迂腐。
更新:
显然,对于最初的问题的答案是“有一个struct uperms_entry
”,因此,编号为1和2的问题是没有意义的。 我很高兴在检查代码之前检查了一下。
背景思考
主要问题解决之后很长时间添加了此部分。
以下是来自ISO / IEC 9899:2011的一些广泛而相关的引用:
§6.2.7兼容types和复合types
¶1如果types相同,则两种types具有兼容types。 确定两种types是否兼容的附加规则在6.7.2中描述了types说明符,在6.7.3中描述了types限定符,在6.7.6中描述了描述符。 55)另外,如果两个单独的翻译单元中声明的结构,联合或枚举types的标签和成员满足以下要求,则两个结构,联合或枚举types是兼容的:如果一个标签被声明,另一个被声明为相同的标签。 如果两者都在各自翻译单位内的任何地方完成,则应遵守以下附加要求:成员之间应有一对一的对应关系,以便每对相应的成员被声明为兼容的types; 如果该对中的一个成员用一个alignment说明符声明,则另一个用等价的alignment说明符声明; 如果这一对中的一个用名字声明,另一个用相同的名称声明。 对于两个结构,相应的成员应按照相同的顺序进行声明。 对于两个结构或联合,相应的位域应具有相同的宽度。 对于两个枚举,相应的成员应具有相同的值。
55)两种types不需要相同即可兼容。
§6.7.2.1结构和联合说明符
¶8在struct-or-union-specifier中存在一个struct-declaration-list在一个翻译单元中声明一个新的types。 struct-declaration-list是结构或联合的成员的一系列声明。 如果struct-declaration-list不包含任何指定的成员,直接或通过匿名结构或匿名联合,行为是未定义的。 types是不完整的,直到在终止列表之后立即完成。
§6.7.2.3标签
¶4具有相同范围并使用相同标记的结构,联合或枚举types的所有声明都声明相同的types。 不pipe在同一翻译单元中是否有标签或types的其他声明,types是不完整的( 129),直到紧接在定义内容的列表的右括号之后,并且此后完成。
¶5在不同范围或使用不同标签的结构体,联合体或枚举types的两个声明声明了不同的types。 不包含标签的结构体,联合体或枚举types的每个声明都声明了不同的types。
¶6表单的types说明符
struct-or-union identifier
opt{ struct-declaration-list }
要么
enum identifier
opt{ enumerator-list }
要么
enum identifier
opt{ enumerator-list , }
声明结构,联合或枚举types。 该列表定义了结构内容,联合内容或枚举内容。 如果提供了标识符, 130)types说明符还将该标识符声明为该types的标签。
¶7表单的声明
struct-or-union identifier ;
指定结构或联合types,并将标识符声明为该types的标签。 131)
¶8如果一个表单的types说明符
struct-or-union identifier
除了作为上述forms之一的一部分之外发生,并且没有其他的标识符作为标签的声明是可见的,则它声明不完整的结构体或联合体types,并且将该标识符声明为该types的标签。 131)
¶9如果一个表单的types说明符
struct-or-union identifier
要么
enum identifier
除了作为上述其中一种forms的一部分之外发生,并且该标识符作为标签的声明是可见的,则它指定与该另一个声明相同的types,并且不重新声明该标签。
¶12示例2为了说明如何使用先前的标签声明来指定一对相互参照的结构,声明
struct s1 { struct s2 *s2p; /* ... */ }; // D1 struct s2 { struct s1 *s1p; /* ... */ }; // D2
指定一对包含彼此指针的结构。 但是请注意,如果s2已经在封闭范围内声明为标签,则声明D1将引用它,而不是D2中声明的标签s2。 为了消除这种情况下的敏感性,声明
struct s2;
可以插在D1之前。 这在内部范围内声明一个新的标签s2; 声明D2然后完成新types的规范。
129)只有在不需要该types的对象的大小时才可以使用不完整的types。 例如,当typedef名称被声明为结构体或联合体的说明符,或者正在声明返回结构体或联合体的指针或函数时,就不需要它了。 (请参阅6.2.5中的不完整types。)在调用或定义此类function之前,必须完成规范。
130)如果没有标识符,则该types可以在翻译单元中仅由其所属的声明引用。 当然,当声明是typedef名称时,后续声明可以使用该typedef名称来声明具有指定结构,联合或枚举types的对象。
131)与枚举类似的结构不存在。
§6.7.3types限定符
¶10对于两个兼容的合格types,两者应具有相同types的合格版本; 说明符或限定符列表中的types限定符的顺序不会影响指定的types。
第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("py = %d, qz = %d\n", py, qz); printf("p1.x = %d\n", p1.x); }
该函数说明了示例2何时适用,但不是合理的代码。 函数中的p1
声明将是与全局variablesp0
相同types的结构。 尽pipe它的types名是struct r1
,但它与局部variablesp
的types是不同的(并且不兼容)。
无论元素名为x
还是y
,都不允许在全局级别重新定义struct r1
。 之前的struct r1;
在这方面是没有任何作用的。
一个有趣的问题是“可以将函数z
传递给任何其他函数(称之为)? 答案是一个合格的“是”,一些限制是有趣的。 (尝试它的代码风格也是惊人的,接近疯狂)。函数必须存在于单独的翻译单元(TU)中。 函数声明必须在函数z
(因为如果它在函数之外,它的原型必须引用在struct r1
外部定义的struct r1
,而不是在里面定义的struct r1
。
在另一个TU中,必须具有一定程度的正确性:函数a
必须具有在其全局范围内可见的兼容结构typesstruct 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("py = %d, qz = %d\n", py, qz); } 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("py = %d, qz = %d\n", py, qz); }
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行是尝试在函数z
定义和初始化struct r2 p
。
这表明在函数中, struct r2
的rn
元素是一个指向在全局范围声明的不完整typesstruct r1
的指针。 添加一个struct r1;
因为函数内部的第一行代码将允许代码编译,但是引用r1p->rn
的初始化是再次拒绝指向不完整types的指针(不完整types是在全局范围声明的struct r1
)。
函数声明和前面的struct r1;
行可以作为不透明的types出现在标题中。 辅助function清单不完整; 需要一种方法来获得一个指向初始化struct r1
的指针,以传入函数,但这是一个细节。
为了使代码在第二个TU中工作,在定义函数之前, struct r1
的types必须在全局范围内完成,并且由于recursion引用,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("py = %d, qz = %d\n", py, qz); } void z(struct r1 *r1p) { struct r2 p = { r1p, 1 }; struct r1 q = { r1p->rn, 2 }; p.rn = &q; printf("py = %d, qz = %d\n", py, qz); }
这个在实现文件中定义结构而在公共头文件中保留不完整types的过程可以在多个实现文件中重复(如果需要的话),但是如果多个TU使用完整的结构定义,则最好放置定义在一个专用头文件中,只在实现这些结构的文件之间共享。 我注意到私人标题是否在公共标题之前或之后并不重要。
也许这对你来说已经很明显了。 之前我不需要考虑这个细节。
在C 1中 ,它们是指相同的types。 C99§6.2.1定义了存在的范围:
2对于标识符指定的每个不同的实体,标识符仅在称为其范围的程序文本区域内可见(即可以使用)。 由相同标识符指定的不同实体或者具有不同的范围,或者位于不同的名称空间中。 有四种范围:函数,文件,块和函数原型。 (函数原型是声明其参数types的函数声明。)
函数范围仅适用于标签(如同一节中稍后所述)。 块范围适用于在块中声明的标识符 – 块由复合语句,迭代语句和select语句(而不是struct
声明或复合初始器)创build。 函数原型范围适用于在函数原型声明中声明的标识符。
这些都不适用于你的例子 – 在你的例子中所有的struct uperms_entry
的提到是在文件范围。
C99§6.7.2.3说:
1具有相同范围并使用相同标记的结构,联合或枚举types的所有声明都声明相同的types。 types是不完整的,直到定义内容的列表的右括号,并在此后完成。
这很清楚,适用于你的情况。
该部分的第8段适用于struct uperms_entry
的第一次提及:
8如果结构体或联合体标识符的formstypes说明符不是以上述forms之一的forms出现,并且没有其他标识符声明作为标签可见,则它声明一个不完整的结构体或联合体types,将该标识符声明为该types的标签。
所以在这一点上,它在文件范围内被声明为不完整的types。 第6段适用于第二次提到struct uperms_entry
:
6 结构体或联合体标识符opt {struct-declaration-list}或枚举标识符{枚举器列表}或枚举标识符{枚举器列表}的types说明符声明结构体,联合体或枚举types。 该列表定义了结构内容,联合内容或枚举内容。 如果提供了一个标识符,那么types说明符还会将该标识符声明为该types的标签。
所以在typedef声明结束之后,它现在是一个完整的types。
附属的问题是没有意义的。
我相信在C ++中情况并非如此。
那么,C99 6.2.5.22说
…未知内容的结构或联合types(如6.7.2.3所述)是不完整的types。 对于所有types的声明,通过在同一范围内声明相同的结构或联合标签及其定义的内容来完成。
这意味着他们是你的情况相同的types。