Reputation: 27375
I'm reading the Standard: N1570 and came across some misunderstanding. I wrote the following simple example:
test.h
:
#ifndef TEST_H
#define TEST_H
extern int second;
#endif //TEST_H
test.c
:
#include "test.h"
enum test_enum{
first,
second
};
But it fails to compile with the error:
error: ‘second’ redeclared as different kind of symbol
second
^~~~~~
This is strange, because Section 6.4.4.3#2
specifies:
2 An identifier declared as an enumeration constant has type int.
I our case the enumeration constant has file scope so I expected it to compile fine.
I rewrote the example above as follows:
main.c
:
extern int second;
int main(int argc, char const *argv[])
{
printf("Second: %d\n", second);
}
And now linker complains:
undefined reference to `second'
Why? It should find the definition in the test.c
because as the Section 6.2.2#5
specifies:
If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.
Upvotes: 0
Views: 85
Reputation: 222486
Your citation of 6.4.4.3 2, that an enumeration constant has type int
, suggests you think that because extern int second
and enum { second }
declare second
to be an int
, that these two declarations of second
may refer to the same thing. That is not correct.
extern int second
declares second
to be the name of an object (a region of memory) that will hold an int
. enum { second }
declares second
to be an enumeration constant which will have a particular value. The enumeration constant has no object (no memory) associated with it. An int
object and an int
constant are different things, and you may not use the same identifier for them in the same scope.
Regarding your question about the link error, “undefined reference to ‘second’”, although test.c
may contain external int second
(since it is provided by the included test.h
, this is not a definition of second
. It is only a declaration, which tells the compiler that the name refers to an object. It does not define the object. Alternatively, if test.c
contains enum { second }
, this only declares second
to be a constant. It does not define an object.
The rules for what is a definition are a bit complicated due to the history of programming language development. For identifiers of objects declared at file scope, there are essentially four cases:
extern
is only a declaration, not a definition. Example: extern int second;
.int second = 2;
.extern
and without an initializer is a tentative definition. If no definition appears in the translation unit (source file being compiled, with all included files), the tentative definition becomes a definition. Example: int second;
.The linkage is not helpful here. The extern int second
in test.c
and the extern int second
in main.c
may refer to the same object due to linkage, but no object for them to refer to was defined. Or, in the alternative, if test.c
contains enum { second }
, then it does not define an object named second
, so there is no object that the extern int second
in main.c
can refer to.
Upvotes: 2
Reputation: 84551
To do what you are attempting, you simply need to refactor you code a bit so that test.[ch]
does not see a redeclaration of second
. The problem is the symbol second
is defined once as extern int second;
and then again as the symbol within the enum
. You can't have both visible within the same file.
To do that you can write test1.h
using a second preprocessor conditional similar to:
#ifndef TEST_H
#define TEST_H
#ifdef USE_ENUM
enum test_enum{
first,
second
};
#else
extern int second;
#endif
#endif
Where depending on whether USE_ENUM
is defined, the code will use either the symbol provided by the enum
, where if not, then you need to define second
in test1.c
#include "test1.h"
#ifdef USE_ENUM
char stub (void) /* stub to prevent empty compilation unit */
{ return 0; }
#else
int second = 2;
#endif
(note the use of a stub
function to prevent an empty compilation unit if USE_ENUM
is defined -- as there would be no code in test1.c
otherwise)
Now all that is required is to include test1.h
in your file containing main()
and passing the compiler define -DUSE_ENUM
as a compiler option depending on which code you want to use, e.g.
#include <stdio.h>
#include "test1.h"
int main (void) {
printf ("second: %d\n", second);
}
Compile Using int second
defined in test.c
Example:
$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c
Example Use/Output
When USE_ENUM
is not defined, then the definition of second
defined in test1.c
and accessed via extern
will result in second
having the value 2
, e.g.
$ ./bin/main1
second: 2
Compile Using enum
defined in test.h
Example:
$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c -DUSE_ENUM
Example Use/Output
When USE_ENUM
is defined, then the value for the symbol second
is provided by the enum
in test1.h
, e.g.
$ ./bin/main1
second: 1
While this is a slight refactoring of what you were attempting, I don't see another way of doing both without using the preprocessor conditional.
Upvotes: 2