了解标识符的链接

Understanding linkage of identifiers

我正在阅读 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 An identifier declared as an enumeration constant has type 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 指定:

If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

要完成您正在尝试的操作,您只需稍微重构一下代码,这样 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提供的符号,如果没有,则需要定义[=81] =] secondtest1.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否则)

现在所需要做的就是在包含 main() 的文件中包含 test1.h 并通过编译器将 -DUSE_ENUM 定义为编译器选项,具体取决于您要使用的代码,例如

#include <stdio.h>
#include "test1.h"

int main (void) {

    printf ("second: %d\n", second);

}

使用 test.c

中定义的 int second 进行编译

示例:

$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c

例子Use/Output

如果未定义 USE_ENUM,则在 test1.c 中定义并通过 extern 访问的 second 的定义将导致 second 具有值2,例如

$ ./bin/main1
second: 2

使用 test.h

中定义的 enum 进行编译

示例:

$ gcc -Wall -Wextra -pedantic -std=c11 -o bin/main1 main1.c test1.c -DUSE_ENUM

例子Use/Output

当定义USE_ENUM时,符号second的值由test1.h中的enum提供,例如

$ ./bin/main1
second: 1

虽然这是对您尝试进行的轻微重构,但我看不到不使用预处理器条件的另一种方法。

对象和常量是不同的东西

你对 6.4.4.3 2 的引用,枚举常量的类型为 int,表明你认为因为 extern int secondenum { second } 声明 second 是一个intsecond 的这两个声明可能指的是同一件事。这是不正确的。

extern int secondsecond 声明为将保存 int 的对象(内存区域)的名称。 enum { second }second 声明为枚举常量,该常量将具有特定值。枚举常量没有与之关联的对象(没有内存)。 int 对象和 int 常量是不同的东西,您不能在同一范围内对它们使用相同的标识符。

并非所有声明都是定义

关于您关于 link 错误的问题,“未定义对‘second’的引用”,尽管 test.c 可能包含 external int second(因为它是由包含的 test.h,这不是 second 的定义。它只是一个声明,它告诉编译器该名称引用一个对象。它不定义对象。或者,如果 test.c 包含 enum { second }, 这只是声明 second 是一个常量。它没有定义一个对象。

由于编程语言发展的历史,定义的规则有点复杂。对于在文件范围内声明的对象的标识符,基本上有四种情况:

  • extern的声明只是声明,不是定义。示例:extern int second;.
  • 带有初始值设定项的声明是一个定义。示例:int second = 2;.
  • 没有 extern 且没有初始值设定项的声明是暂定定义。如果翻译单元(正在编译的源文件,包含所有包含的文件)中没有出现定义,则暂定定义成为定义。示例:int second;.

link年龄在这里没有帮助。 test.c中的extern int secondmain.c中的extern int second可能由于linkage引用了同一个对象,但是没有定义它们引用的对象.或者,换句话说,如果 test.c 包含 enum { second },那么它没有定义一个名为 second 的对象,因此 [=38= 中的 extern int second 没有对象] 可以参考。