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:
- 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?
(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.
Background thinking
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
", p.y, q.z);
printf("p1.x = %d
", 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
", 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
", 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
t