Can you give chapter and verse from one of the three C standards (preferably C99 or C11) which indicates whether the following header file has one or two struct uperms_entry
types 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 */
Adjunct questions:
(I think the answers are 'yes — strictly there are two types', and then (1) No and (2) No.)
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:
Clearly, the answer to the initial question is 'there is one struct uperms_entry
' and therefore the questions numbered 1 and 2 are moot. I'm glad I checked before throwing a hissy fit in a code review.
This section was added long after the primary question 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 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 identifier
opt{ struct-declaration-list }
or
enum identifier
opt{ enumerator-list }
or
enum identifier
opt{ 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.
I was aware of Example 2 when I wrote the question. This is some thinking out loud about some of 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("p.y = %d, q.z = %d\n", p.y, q.z);
printf("p1.x = %d\n", p1.x);
}
The function illustrates when Example 2 applies, but is not sensible
code. The declaration of p1
in the function is would be a structure
of the same type as the global variable p0
. Even though its type name
is struct r1
, it is of a different (and incompatible) type from the
type of the local variable p
.
The redefinition of struct r1
at the global level is not allowed,
regardless of whether the element is named x
or y
. The prior
struct r1;
is a no-op in this context.
One interesting issue is 'can function z
pass p
or q
to any other
function (call it a
)? The answer is a qualified 'yes', and some of
the constraints are interesting. (It would also be appalling coding
style to try it, verging on the insane.) The function must exist in a
separate translation unit (TU). The function declaration must be inside
function z
(because if it is outside the function, its prototype must
refer to the struct r1
defined outside the function, not the struct
r1
defined inside.
In the other TU, a degree of sanity must prevail: the function a
must
have the compatible structure types struct r1
and struct r2
visible
in its global scope.
Here's another example, but this one 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("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);
}
The warnings from GCC 4.7.1 on Mac OS X 10.7.4 are:
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
the attempt to define and initialize struct r2 p
in function z
.
This demonstrates that within the functions, the rn
element of struct
r2
is a pointer to the incomplete type struct r1
declared at the
global scope. Adding a struct r1;
as the first line of code inside
the function would allow the code to compile, but the initialization
referencing r1p->rn
is derefencing a pointer to an incomplete type
again (the incomplete type is the struct r1
declared at the global
scope).
The function declarations and the preceding struct r1;
line could
appear in a header as an opaque type. The list of supporting functions
is incomplete; there'd need to be a way to get a pointer to an
initialized struct r1
to pass into the functions, but that's a detail.
To make the code work in this second TU, the types for struct r1
must
be complete in the global scope before the functions are defined, and
because of the 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("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);
}
This process of defining the structures in the implementation file while leaving the type incomplete in the public header file can be repeated in multiple implementation files if necessary, though if more than one TU uses the complete structure definition, it would be better to place the definitions in a private header file shared only between the files that implement the structures. I note that it does not matter whether the private header precedes or follows the public header.
Maybe this was all obvious to you already. I'd not needed to think it through in this level of detail before.
In C1, they refer to the same type. C99 §6.2.1 defines the scopes that exist:
2 For each different entity that an identifier designates, the identifier is visible (i.e., can be used) only within a region of program text called its scope. Different entities designated by the same identifier either have different scopes, or are in different name spaces. There are four kinds of scopes: function, file, block, and function prototype. (A function prototype is a declaration of a function that declares the types of its parameters.)
Function scope only applies to labels (as explicitly stated later in the same section). Block scope applies to identifiers declared in blocks - blocks are created by compound-statements, iteration-statements and selection-statements (and not by struct
declarations or compound initialisers). Function prototype scope applies to identifiers declared within a function prototype declaration.
None of these apply to your example - all of the mentions of struct uperms_entry
in your example are at file scope.
C99 §6.7.2.3 says:
1 All declarations of structure, union, or enumerated types that have the same scope and use the same tag declare the same type. The type is incomplete until the closing brace of the list defining the content, and complete thereafter.
This is pretty clear, and applies to your case.
Paragraph 8 of that section applies to the first mention of struct uperms_entry
:
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.
So at that point it's declared as an incomplete type at file scope. Paragraph 6 applies to the second mention of struct uperms_entry
:
6 A type specifier of the form struct-or-union identifieropt { struct-declaration-list } or enum identifier { enumerator-list } or enum identifier { 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, the type specifier also declares the identifier to be the tag of that type.
So after the }
at the end of that typedef declaration, it's now a complete type.
The adjunct questions are moot.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With