Le 17/06/2005 02:59, dans BED7ED1D.4F22%
[email protected],
See 6.9.2 also. The standard allows one _definition_ of an object (or
function) name with external linkage and multiple declarations that
refer to it. A declaration with an initializer is always a definition.
A declaration with no initializer and 'extern' is only a declaration.
An object declaration with no initializer and no storage-class
specifier is a 'tentative definition' which magically is turned into a
definition with value zero unless overridden by a definite definition
in the same translation unit which is of course also a definition. And
doing either of those in more than one t.u. is Undefined Behavior.
(This particular U.B. is caught by *some* implementations.)
For functions it is simpler because the syntax differs. A declaration
with just a semicolon is just a declaration; something that looks
syntactically like a declaration followed by a braced block aka
compound statement is a definition. 'extern' is always redundant,
though some people consider it good style.
About different types: the standard says the identifiers represent the same
object, however section 6.7 says
"All declarations in the same scope that refer to the same object or
function shall specify compatible types."
So same object doesn't necessarily mean same type ? (otherwise why enforce
that restriction ?). That would mean the "int c","char c" example is
allowed.
Scope only applies within a translation unit (preprocessed source
file), and automatic variables may not be redeclared within the same
scope at all, so this actually applies only to multiple declarations
at file-scope of a variable or function -- or multiple declarations of
an 'extern' variable within a block, which is pretty obscure and
usually considered bad style. Yes that variable or function always has
the same type; that's precisely why the declarations must be
compatible -- and since 6.7p4 is a constraint, if your code violates
it the compiler must issue a diagnostic. Compatible, rather than
identical, allows some of the declarations to _omit_ some information:
int a[20];
int a[10]; // ERROR: incompatible
int a[]; // compatible
void f (); // old-style doesn't specify parameters
void f (int a, char * b); // compatible
int f (); // ERROR: incompatible
This may not seem useful because one doesn't normally think of just
writing more than one declaration for the same thing. There are two
places it is, though. First, pretty rare, is if there is a dependency
cycle in initializers so that you must declare some object and use it
before you can write its initializer. The other, much more common, is
when you have a header file that declares the functions and (possibly)
objects "exported" from a module for use by other t.u.s. Since a
definition is also a declaration, if you #include that header
(typically .h) into the source file that implements those entities
(.c) then the compiler will warn you of any mistakes where an object
or a prototyped function does not agree with the promised interface.
What applies between t.u.s is linkage. If a given name is declared in
different t.u.s with external linkage, both/all declarations (and the
code using them) refer to the same object or function. If an object X
actually has (is defined with) type A, but you access it through an
lvalue having an incompatible type B (from a wrong declaration) it is
Undefined Behavior (which is not required to be and rarely is
diagnosed) except for a few special cases in 6.5p7 and 6.5.2.3p5. And
even for those (former) it is possible that the stored representations
for some values of type A may be trap representations for type B and
fetching such also causes Undefined Behavior. But if B is unsigned
char, or plain char on a system where (as on many) that is unsigned,
*that* access is safe -- although it may or may not be useful.
- David.Thompson1 at worldnet.att.net