Does the C standard consider that there are one or two types of struct uperms_entry in this header?

Can you give a chapter and a verse from one of the three C standards (preferably C99 or C11) that indicates whether the following header file has one or two types of struct uperms_entry in it?

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

Adjunctive Issues:

  • If there are two types, is there a way to get GCC to report this issue?
  • If there are two types, does it really matter in practice?

(I think the answers are “yes” - there are strictly two types, and then (1) “No” and (2) “no.”)

Context: view internal code. I would like the order of the structures to be canceled, but I'm not sure that I am completely too pedantic.

Update:

It is clear that the answer to the initial question is: “there is one struct uperms_entry ”, and therefore questions with numbers 1 and 2 are controversial. I am glad that I checked before throwing a hiss in the code review.

Background thinking

This section was added long after the main issue was resolved.


Here are some extensive but relevant quotes from ISO / IEC 9899: 2011:

§6.2.7 Compatible type and composite type

¶1 Two types have a compatible type if their types are the same. Additional rules for determining the compatibility of two types: those described in 6.7.2 for type specifiers, 6.7.3 for type classifiers, and 6.7.6 for declarators. 55) In addition, two structures, a union, or the listed types declared in separate translation units are compatible if their tags and elements satisfy the following requirements: If one is declared with a tag, the other must be declared with the same tag. If both of them are filled in at any place of their unit conversion, then the following additional requirements apply: a one-to-one correspondence between their members, so that each pair of the corresponding members is declared with compatible types; if one member of a pair is declared using the alignment specifier, the other is declared with an equivalent alignment specifier; and if one of the members of the couple is declared with a name, the other is declared with the same name. For two structures, the corresponding members must be declared in the same order. For two structures or associations, the corresponding bit fields must have the same width. For two enumerations, the corresponding members must have the same value.

55) Two types do not have to be identical.

§6.7.2.1 Structure and Association Specifications

¶8 The presence of the struct-declaration-list in the struct-or-union-specifier declares a new type in the translation Block. A struct-declaration-list is a sequence of declarations for members of a structure or union. If the list struct-declaration-list does not contain any named members, either directly or by an anonymous structure or an anonymous union, the behavior is undefined. The type is incomplete until after } , which completes the list, and after that.

§6.7.2.3 Tags

¶4 All declarations of a structure, union, or enumerated type that have the same scope and the same tags declare the same type. regardless of whether there is a tag or what other type declarations are in the same translation unit, type incomplete 129) until immediately after the closing bracket of the list that defines the content, and ends after that.

¶5 Two declarations of a structure, union, or enumerated type, which in different areas or use different tags, declare different types. each declaration of a structure, union, or enumerated type that does not include a tag declares a separate type.

¶6 Form type specifier

struct-or-union identifier opt { struct-declaration-list }

or

enum identifier opt { enumerator-list }

or

enum identifier opt { enumerator-list , }

Declares a structure, union, or numbered type. A list defines the contents of a structure, the contents of a union, or the contents of an enumeration. If the identifier, 130) the type specifier also declares the identifier must be a tag of this type.

¶7 Form Announcement

 struct-or-union identifier ; 

indicates the structure or type of union and declares the identifier as a tag of this type. 131)

¶8 If the form type specifier

 struct-or-union identifier 

happens differently 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 type of union, and declares the identifier as a tag of this type. 131)

¶9 If the form type specifier

 struct-or-union identifier 

or

 enum identifier 

happens differently than as part of one of the above forms, and the declaration of the identifier as a tag, it indicates the same type as this other declaration, and does not update the tag.

¶12 EXAMPLE 2 To illustrate the use of a preliminary tag declaration to indicate a pair of interconnected structures, declarations

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

specify a pair of structures containing pointers to each other. Note, however, that if s2 is already declared as a tag in the scope, the D1 declaration will refer to it, and not the s2 tag declared in D2. To eliminate this contextual sensitivity, Ad

 struct s2; 

can be inserted before D1. This declares a new s2 tag in the internal scope; the D2 declaration then completes the specification of the new type.

129) An incomplete type can be used only when the size of an object of this type is not needed. This is not necessary, for example, when a typedef is declared as a specifier for a structure or union, or when a pointer to or a function returns a structure or union is declared. (See Incomplete types in 6.2.5.) The specification must be completed before such a function is called or defined.

130) If there is no identifier, the type may be within the translation unit, only to which the declaration refers, Except. Of course, when the declaration has a name of typedef, subsequent declarations can use this name of typedef to declare objects that have the specified structure, union, or numbered type.

131) There is no such construction with a listing.

§6.7.3 Model classifiers

¶10 For two qualified types to be compatible, both must have an identical version of the compatible type; the recipient of something type classifiers in the list of qualifiers or qualifiers do not affect the specified type.

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


I was aware of Example 2 when I wrote the question. These are some thinking out loud about what the information above means.

Consider this example, which compiles cleanly:

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

The function illustrates when Example 2 is applied, but the code is not reasonable. Declaring a function p1 in a function would be a structure of the same type as the global variable p0 . Although the name of this type is struct r1 , it differs from the (and incompatible) type from the type of the local variable p .

Redefining struct r1 at the global level is not allowed, regardless of whether an x or y element is specified. Preliminary struct r1; is a non-op in this context.

One interesting problem: “can z pass p or q function for any other (call it a )? The answer is yes, and some of the restrictions are interesting. (It would also be a terrifying coding style to try it bordering on crazy.) The function must exist in a separate translation unit (TU). The function declaration must be inside the z function (because if it is outside the function, its prototype must refer to struct r1 defined outside the function, and not to struct r1 specified inside.

In another TU, the degree of sanity should prevail: the function a should have compatible structure types struct r1 and struct r2 visible on a global scale.

Here is another example, but it does not compile:

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

Warnings from GCC 4.7.1 on 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] 

Lines 13 is the assignment p.rn = &q; in function y , and line 23 is an attempt to define and initialize struct r2 p in function z .

This demonstrates that in functions, the rn struct r2 element is a pointer to the incomplete type struct r1 declared for global reach. Adding struct r1; as the first line of code inside the function, the code will be compiled, but initializing the link r1p->rn cancels the pointer to the incomplete type again (the incomplete type is struct r1 , declared in the global scope).

Function declarations and the previous line struct r1; may appear in the header as opaque. The list of support functions is incomplete; There must be a way to get a pointer to an initialized struct r1 in order to go into functions, but what a detail.

To make code work in this second TU, the types for struct r1 must be complete globally before functions are defined, and because of recursive references, `struct r21 must also be complete.

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

This process of defining structures in the implementation file while leaving the type incomplete in the public header file can be repeated if necessary, several implementation files, although if several TUs use the full definition of the structure, it would be better to place the definitions in a closed header file, shared only between files that implement structures. I note that it doesn't matter if the private heading precedes or follows the public heading.

Perhaps this was already obvious to you. I did not need to think about it through this level of detail before.

+4
source share
2 answers

In C 1, they are of the same type. C99 §6.2.1 defines the existing areas:

2 For each individual object that is indicated by an identifier (that is, it can be used) only within the region, the program text is called its region. Different objects denoted by the same identifier has different scopes or has a different name for the space. There are four types of areas: function, file, block, and function prototype. (A function prototype is a function declaration that declares the types of its parameters.)

The scope of functions applies only to labels (as indicated later in the same section). A block scope applies to identifiers declared in blocks — blocks are created by compound statements, iterative statements, and select statements (rather than struct declarations or compound initializers). The function prototype area applies to identifiers declared in the function prototype declaration.

None of them apply to your example - all references to struct uperms_entry in your example are file struct uperms_entry .

C99 §6.7.2.3 states:

1 All declarations of a structure, union, or enumerated type that have the same scope and the same tags declare the same type. The type is incomplete until the parenthesis of the list that defines the contents is closed and ends after that.

This is pretty clear and relevant to your case.

Clause 8 of this section refers to the first mention of struct uperms_entry :

8 If a specifier of the type identifier of the structure or the union of the form appears, except as part of one of the above forms, and no other declaration of the identifier as a tag, it declares an incomplete structure or union, and declares the identifier as a tag of this type.

So, at this moment it is declared as an incomplete type in the file area. Clause 6 applies to the second mention of struct uperms_entry :

6 Form type specifier struct-or-union identifier opt {struct-declaration-list} or enumeration identifier {enumerator-list} or enumeration identifier {list of enumerators,} declares a structure, union, or numbered type. A list defines the contents of a structure, the contents of a union, or the contents of an enumeration. If the identifier, the type specifier also declares the identifier will be a tag of this type.

So after } at the end of this declaration, typedef is now the full type.

Issues related to the application are controversial.


1. I believe that this is not the case in C ++.
+6
source

OK, C99 6.2.5.22 says

... The structure or type of association of unknown content (as described in 6.7.2.3) is an incomplete type. It is populated for all ads of this type, declaring the same structure or association tag as its own, defining content later in the same area.

This would mean that they are the same for your case.

+2
source

All Articles