【问题标题】:Understanding linkage of identifiers了解标识符的链接
【发布时间】:2018-12-01 05:40:53
【问题描述】:

我正在阅读 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 指定:

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

【问题讨论】:

  • 您也可以参考带有C11 Standard - Draft n1570的网页,以防止每个人每次都下载整个.pdf(两者都可以,只是一个建议)
  • 你包括test.h,其中包含extern int second; 当你声明一个enum时,标签(枚举成员)也是全局常量——编译器看到两个second 的声明——因此是重新声明。
  • @DavidC.Rankin 问题是如果我用int 替换枚举常量的声明,它会按预期工作。
  • 要么将 enum 移动到标题并在 main 中包含 test.h,要么将变量 second 重命名为 test.h 中的其他名称并在 test.c 中定义它。
  • 第一个 sn-p 需要不同类型的不同名称空间的概念,因此标识符不会有歧义。 C 仅适用于结构成员。早期的 K&R 没有,非常痛苦。值得注意的是,C++ 最近用枚举类修复了这个问题。第二个 sn-p 在所有代码中都有 extern (“它存在于其他地方”),链接器正确地抱怨“在哪里?”删除 main.c 文件中的 extern 说“这里”。

标签: c enums linkage


【解决方案1】:

对象和常量是不同的东西

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

extern int secondsecond 声明为将保存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;

链接在这里没有帮助。 test.c 中的extern int secondmain.c 中的extern int second 由于链接可能会引用同一个对象,但没有定义它们可以引用的对象。或者,如果test.c包含enum { second },那么它没有定义一个名为second的对象,所以main.c中的extern int second没有可以引用的对象。

【讨论】:

  • 为什么枚举常量不是对象?第 3.15 节指定 object 是 region of data storage in the execution environment, the contents of which can represent values 这里没有关于 enum 的内容...或者它指定 enumeration constant/constant 不是 object?
  • @St.Antario:根据 C 2018 6.2.5 16,枚举是一组命名整数值。所以枚举常量只是一个值的名称。它不是内存区域的名称。当你在源代码中使用它时,编译器的唯一义务就是把它当作那个值——它没有义务为它保留内存。此外,它没有地址。您可以获取对象的地址,但不能获取枚举常量的地址。
【解决方案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提供的符号,如果没有,则需要定义 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

(注意如果定义了USE_ENUM,则使用stub 函数来防止空编译单元——否则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_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_ENUM 时,符号second 的值由enum 中的test1.h 提供,例如

$ ./bin/main1
second: 1

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-30
    • 2014-02-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多