了解标识符的链接

时间:2018-12-01 05:40:53

标签: c enums linkage

我正在阅读the StandardN1570,遇到了一些误会。我写了以下简单示例:

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指定的那样:

  

如果对象标识符的声明具有文件范围,并且   没有存储类说明符,其链接是外部的。

2 个答案:

答案 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 secondenum { 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 secondtest.c中的extern int second由于链接而可能引用相同的对象,但未定义它们要引用的对象。或者,如果main.c包含test.c,则它没有定义名为enum { second }的对象,因此{{1}中没有second的对象}可以参考。