我正在阅读the Standard:N1570,遇到了一些误会。我写了以下简单示例:
test.h
:
#ifndef TEST_H
#define TEST_H
extern int second;
#endif //TEST_H
test.c
:
#include "test.h"
enum test_enum{
first,
second
};
但是它无法编译并显示错误:
error: ‘second’ redeclared as different kind of symbol
second
^~~~~~
这很奇怪,因为第6.4.4.3#2
节指定:
2声明为枚举常量的标识符的类型为int。
在这种情况下,枚举常量具有文件范围,因此我希望它可以正常编译。
我将以上示例重写如下:
main.c
:
extern int second;
int main(int argc, char const *argv[])
{
printf("Second: %d\n", second);
}
现在链接器抱怨:
undefined reference to `second'
为什么?它应该在test.c
中找到定义,因为正如Section 6.2.2#5
指定的那样:
如果对象标识符的声明具有文件范围,并且 没有存储类说明符,其链接是外部的。
答案 0 :(得分:2)
要做您想做的事情,您只需要重构一下代码,以使test.[ch]
不会看到second
的重新声明。问题在于符号second
被一次定义为extern int second;
,然后又被定义为enum
中的符号。您不能在同一文件中同时看到这两者。
为此,您可以使用类似于以下条件的第二个预处理器来编写test1.h
:
#ifndef TEST_H
#define TEST_H
#ifdef USE_ENUM
enum test_enum{
first,
second
};
#else
extern int second;
#endif
#endif
根据是否定义了USE_ENUM
,代码将使用enum
提供的符号,如果没有,则需要 define {{1 }}在second
test1.c
(请注意,如果定义了#include "test1.h"
#ifdef USE_ENUM
char stub (void) /* stub to prevent empty compilation unit */
{ return 0; }
#else
int second = 2;
#endif
,请使用stub
函数来防止空编译单元,因为USE_ENUM
中将没有代码否则)
现在所需要做的就是在包含test1.c
的文件中包含test1.h
并根据您要使用的代码将编译器定义main()
传递为编译器选项,例如< / p>
-DUSE_ENUM
使用#include <stdio.h>
#include "test1.h"
int main (void) {
printf ("second: %d\n", second);
}
中定义的int second
示例:
test.c
使用/输出示例
如果未定义$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c
,则在USE_ENUM
中定义并通过second
访问的test1.c
的定义将导致extern
的值为{{ 1}},例如
second
使用2
中定义的$ ./bin/main1
second: 2
示例:
enum
使用/输出示例
定义了test.h
时,符号$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c -DUSE_ENUM
的值由USE_ENUM
中的second
提供,例如
enum
虽然这是对您尝试的内容的轻微重构,但我认为没有不使用预处理器有条件的情况,这两种方法都不会实现。
答案 1 :(得分:2)
您对6.4.4.3 2的引用是枚举常量的类型为int
,这表明您认为因为extern int second
和enum { second }
声明second
为{{ 1}},int
的这两个声明可以引用同一件事。那是不正确的。
second
声明extern int second
是将保存second
的对象(内存区域)的名称。 int
声明enum { second }
是一个枚举常量,它将具有特定的值。枚举常量没有与之关联的对象(没有内存)。 second
对象和int
常量是不同的东西,您可能无法在同一范围内为它们使用相同的标识符。
关于链接错误的问题,“未定义的对“第二个”的引用”,尽管int
可能包含test.c
(由于它是由随附的external int second
提供的,所以不是test.h
的定义,它只是一个声明,它告诉编译器该名称引用的是对象,它没有定义该对象;或者,如果second
包含test.c
,则此声明仅声明enum { second }
为常量,没有定义对象。
定义的规则由于编程语言开发的历史而有些复杂。对于在文件作用域声明的对象的标识符,基本上有四种情况:
second
的声明只是一个声明,而不是定义。例如:extern
。extern int second;
。int second = 2;
且没有初始化程序的声明是一个临时定义。如果翻译单元中没有定义(正在编译源文件,包括所有包含的文件),则暂定定义将成为定义。例如:extern
。链接在这里没有帮助。 int second;
中的extern int second
和test.c
中的extern int second
由于链接而可能引用相同的对象,但未定义它们要引用的对象。或者,如果main.c
包含test.c
,则它没有定义名为enum { second }
的对象,因此{{1}中没有second
的对象}可以参考。