I would like a standard reference why the following code triggers a compliance warning in C (tested with gcc -pedantic
; \"typedef redefinition\"), but is fine i
Because the C++ Standard explicitly says so.
Reference:
C++03 Standard 7.1.3 typedef specifier
§7.1.3.2:
In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.
[Example:
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
—end example]
typedef
names have no linkage and C99 standard disallows identifiers with no linkage specification to have more than one declaration with the same scope and in the same name space.
Reference:
C99 Standard: §6.2.2 Linkages of identifiers
§6.2.2/6 states:
The following identifiers have no linkage: an identifier declared to be anything other than an object or a function; an identifier declared to be a function parameter; a block scope identifier for an object declared without the storage-class specifierextern.
Further §6.7/3 states:
If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except for tags as specified in 6.7.2.3.
You can do it in C++ because of 7.1.3/3 and /4.
You can't do it in C99 because it doesn't have any equivalent special case in 6.7.7, so re-declaring a typedef name follows the same rules as re-declaring any other identifier. Specifically 6.2.2/6 (typedefs have no linkage) and 6.7/3 (identifiers with no linkage can only be declared once with the same scope).
Remember typedef
is a storage-class-specifier in C99, whereas in C++ it's a decl-specifier. The different grammar leads me to suspect that the C++ authors decided to put more effort into making typedefs "a different kind of declaration", and so may well have been willing to spend more time and text on special rules for them. Beyond that I don't know what the C99 authors' (lack of) motivation was.
[Edit: see Johannes's answer for C1x. I'm not following that at all, so I should probably stop using "C" to mean "C99" because I probably won't even notice when they ratify and publish. It's bad enough as it is: "C" should mean "C99", but in practice means "C99 if you're lucky, but if you have to support MSVC then C89".]
[Edit again: and indeed, it has been published and is now C11. Woot.]
The 2011 C standard was published on Monday 2011-12-19 by ISO (or, more precisely, the notice that it had been published was added to the committee web site on the 19th; the standard may have been published as 'long ago' as 2011-12-08). See the announcement at the WG14 web site. Sadly, the PDF from ISO costs 338 CHF, and from ANSI 387 USD.
The question is "Are repeated typedefs allowed in C"? The answer is "No - not in the ISO/IEC 9899:1999 or 9899:1990 standards". The reason is probably historical; the original C compilers did not allow it, so the original standardizers (who were mandated to standardize what was already available in C compilers) standardized that behaviour.
See the answer by Als for where the C99 standard proscribes repeated typedefs. The C11 standard has changed the rule in §6.7 ¶3 to:
3 If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except that:
- a typedef name may be redefined to denote the same type as it currently does, provided that type is not a variably modified type;
- tags may be redeclared as specified in 6.7.2.3.
So there is now an explicit mandate a repeated typedef in C11. Roll on the availability of C11-compliant C compilers.
For those still using C99 or earlier, the follow-up question is then, presumably "So how do I avoid running into problems with repeated typedefs?"
If you follow the rule that there is a single header that defines each type that is needed in more than one source file (but there can be many headers defining such types; each separate type is found in only one header, though), and if that header is used any time that type is needed, then you don't run into the conflict.
You can also use incomplete structure declarations if you only need pointers to the types and don't need to allocate the actual structure or access the members of them (opaque types). Again, set rules about which header declares the incomplete type, and use that header wherever the type is needed.
See also What are extern variables in C; it talks about variables, but types can be treated somewhat similarly.
I very much need the "incomplete structure declarations", on account of separate preprocessor complications that prohibit certain inclusions. So you're saying that I must not typedef those forward declarations if they're typedefed again by the full header?
More or less. I've not really had to deal with this (though there are parts of the systems at work that get very close to having to worry about it), so this is a little tentative, but I believe it should work.
Generally, a header describes the external services provided by a 'library' (one or more source files) in sufficient detail for the users of the library to be able to compile with it. Especially in the case where there are multiple source files, there may also be an internal header that defines, for example, the complete types.
All headers are (a) self-contained and (b) idempotent. That means you can (a) include the header and all the required other headers are automatically included, and (b) you can include the header multiple times without incurring the wrath of the compiler. The latter is usually achieved with header guards, though some prefer #pragma once
- but that is not portable.
So, you can have a public header like this:
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
#include <stddef.h> // size_t
typedef struct mine mine;
typedef struct that that;
extern size_t polymath(const mine *x, const that *y, int z);
#endif /* PUBLIC_H_INCLUDED */
So far, so not very controversial (though one can legitimately suspect that the interface provided by this library is very incomplete).
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include "public.h" // Get forward definitions for mine and that types
struct mine { ... };
struct that { ... };
extern mine *m_constructor(int i);
...
#endif /* PRIVATE_H_INCLUDED */
Again, not very controversial. The public.h
header must be listed first; this provides an automatic check of self-containment.
Any code that needs the polymath()
services writes:
#include "public.h"
That is all the information that's needed to use the service.
Any code in the library that defines the polymath()
services writes:
#include "private.h"
Thereafter, everything functions as normal.
If there's another library (call it multimath()
) that uses the polymath()
services, then that code gets to include public.h
just like any other consumer. If the polymath()
services are part of the external interface to multimath()
, then the multimath.h
public header will include public.h
(sorry, I switched terminologies near the end, here). If the multimath()
services completely conceal the polymath()
services, then the multimath.h
header won't include public.h
, but the multimath()
private header might well do so, or the individual source files that need the polymath()
services can include it when needed.
As long as you religiously follow the discipline of including the correct header everywhere, then you won't run into double-definition trouble.
If you subsequently find that one of your headers contains two groups of definitions, one which can be used without conflict and one which can sometimes (or always) conflict with some new header (and the services declared therein), then you need to split the original header into two sub-headers. Each sub-header individually follows the rules elaborated here. The original header becomes trivial - a header guard and lines to include the two individual files. All existing working code remains untouched - though the dependencies change (extra files to depend on). New code can now include the relevant acceptable sub-header while also using the new header that conflicts with the original header.
Of course, you can have two headers that are simply irreconcilable. For a contrived example, if there is a (badly designed) header that declares a different version of the FILE
structure (from the version in <stdio.h>
), you are hosed; code can include either the badly designed header or <stdio.h>
but not both. In this case, the badly designed header should be revised to use a new name (perhaps File
, but perhaps something else). You might more realistically run into this trouble if you have to merge code from two products into one after a corporate takeover, with some common data structures, such as DB_Connection
for a database connection. In the absence of the C++ namespace
feature, you are stuck with a renaming exercise for one or both lots of code.
There is nothing in the c spec that says why this is invalid. The spec is the wrong place to clarify that. FWIW it is allowed in C1x (according to an answer i received to one of my last questions).
I suppose that this c1x feature supports transforming macros to typedefs (the former are allowed to be repeated if identically).
A lot of people have answered, referring to the standards, but nobody said, WHY the standards differ for C and C++ here. Well, I believe, the reason for allowing repeated typedefs in C++ was, that C++ implicitely declares structures and classes as types. So the following is legal in C++:
struct foo { int a; int b; };
foo f;
In C, one has to write:
struct foo { int a; int b; };
typedef struct foo foo;
foo f;
There is a lot of C code like that, that declares structs as types. If such code is migrated to C++, the typedefs become duplicate, because the C++ language adds its own implicit typedefs. So, to avoid programmers the hassle to remove those no longer required typedefs, they allowed duplicate typedefs in C++ from the beginning.
As others have said, people with time realized, that allowing repeated identical typedefs in C could also be usefull. At least, it shouldn't harm. That's why this C++ feature got sort of "backported" into C11.