【问题标题】:Linkage of extern block scope variable, C外部块范围变量的链接,C
【发布时间】:2016-12-20 20:34:03
【问题描述】:

C 标准说:

对于使用存储类说明符 extern 声明的标识符 该标识符的先前声明可见的范围,31) 如果先前的声明指定了内部或外部链接,则 在后面的声明中标识符的链接与 在先前声明中指定的链接。如果没有事先声明 可见,或者如果先前的声明没有指定链接,则 标识符有外部链接。

尚不清楚要考虑的先前标识符是否必须具有相同的类型(注意:C++ 标准明确表示“具有相同名称和类型的实体”)。例如:

static int a;   // internal linkage

void f()
{
    float a;      // no linkage, instead of 'int a' we have 'float a'

    {
        extern int a;     // still external linkage? or internal in this case?
        a = 0;            // still unresolved external?
    }
}

我尝试使用不同的编译器对其进行测试,但似乎链接主题并不是一个团结一致的主题。

【问题讨论】:

  • 可能是 6.7/4:“同一范围内引用同一对象或函数的所有声明都应指定兼容的类型。”
  • @KerrekSB - 是的。这基本上应该是一个答案:)

标签: c extern linkage


【解决方案1】:

我想我有一个答案。我会写下一般的链接主题。

C 标准说:

在翻译单元集合中,每个声明都有一个特定的 带有外部链接的标识符表示相同的实体(对象或函数)。 在一个翻译单元中,标识符的每个声明都带有 内部链接表示同一实体。

C++ 标准说:

当名称具有外部链接时,它所表示的实体可以是 由来自其他翻译单元范围的名称或来自 同一翻译单元的其他范围。当一个名字有内部 链接,它表示的实体可以通过其他名称引用 同一个翻译单元中的作用域。

这有两个含义:

  • 在翻译单元集合中,我们不能有多个不同的同名外部实体(C++ 中的重载函数除外),因此表示单个外部实体的每个声明的类型应该一致。我们可以检查类型是否在一个翻译单元内一致,这是在编译时完成的。我们无法在编译时和链接时检查不同翻译单元之间的类型是否一致。

从技术上讲,在 C++ 中,我们可以在没有函数重载的情况下违反 in the set of translation units we cannot have multiple distinct external entities with the same name 规则。由于 C++ 具有对类型信息进行编码的名称修饰,因此可以有多个具有相同名称和不同类型的外部实体。例如:

file-one.cpp:

int a;                // C decorated name: _a
                      // C++ decorated name (VC++): ?a@@3HA

//------------------------------------------------

file-two.cpp:

float a;              // C decorated name: _a
                      // C++ decorated name (VC++): ?a@@3MA

而在 C 中,这实际上是一个外部实体,第一单元中的代码会将其视为 int,而第二单元中的代码会将其视为 float

  • 在一个翻译单元中,我们不能有多个不同的同名内部实体(C++ 中的重载函数除外),因此表示单个内部实体的每个声明的类型应该一致。我们检查翻译单元内的类型是否一致,这是在编译时完成的。

现在我们将更接近这个问题。

C++ 标准说:

在块范围内声明的函数的名称和一个 由块范围外部声明声明的变量具有链接。如果 存在具有链接的实体的可见声明 相同的名称和类型,忽略在最里面声明的实体 封闭命名空间范围,块范围声明声明 相同的实体并接收前一个声明的链接。如果 有不止一个这样的匹配实体,程序是 格式不正确。否则,如果没有找到匹配的实体,则块范围 实体接收外部链接。

// C++

int a;                // external linkage

void f()
{
    extern float a;         // external linkage
}

这里我们没有先前声明具有相同名称 (a) 和类型 (float) 的实体,因此 extern float a 的链接是外部的。由于我们在这个翻译单元中已经有带有外部链接的int a 并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。

// C++

static int a;             // internal linkage

void f()
{
    extern float a;       // external linkage
}

这里我们没有先前声明具有相同名称 (a) 和类型 (float) 的实体,因此 extern float a 的链接是外部的。这意味着我们必须在另一个翻译单元中定义float a。请注意,我们在一个翻译单元中具有具有外部和内部链接的相同标识符(我不知道为什么 C 会考虑这种未定义的行为,因为我们可以在不同的翻译单元中拥有同名的内部和外部实体)。

// C++ (example from standard)

static int a;        // internal linkage

void f()
{
    int a;            // no linkage

    {
        extern int a;    // external linkage
    }
}

这里之前的声明int a 没有链接,所以extern int a 有外部链接。这意味着我们必须在另一个翻译单元中定义int a

C 标准说:

对于使用存储类说明符 extern 声明的标识符 该标识符的先前声明可见的范围,31) 如果先前的声明指定了内部或外部链接,则 在后面的声明中标识符的链接与 在先前声明中指定的链接。如果没有事先声明 可见,或者如果先前的声明没有指定链接,则 标识符有外部链接。

所以我们可以看到在 C 中只考虑名称(不考虑类型)。

// C

int a;                // external linkage

void f()
{
    extern float a;         // external linkage
}

这里之前声明的标识符a有外部链接,所以extern float a的链接是一样的(外部的)。由于我们在这个翻译单元中已经有了带有外部链接的int a,并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。

// C

static int a;             // internal linkage

void f()
{
    extern float a;       // internal linkage
}

这里之前声明的标识符a有内部链接,所以extern float a的链接是一样的(内部的)。由于我们在这个翻译单元中已经有了带有内部链接的static int a,并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。而在 C++ 中,这段代码很好(我认为添加类型匹配要求时考虑了函数重载)。

// C

static int a;        // internal linkage

void f()
{
    int a;            // no linkage

    {
        extern int a;    // external linkage
    }
}

这里之前声明的标识符a没有链接,所以extern int a有外部链接。这意味着我们必须在另一个翻译单元中定义int a。然而 GCC 决定以 variable previously declared 'static' redeclared 'extern' 错误拒绝此代码,可能是因为根据 C 标准我们有未定义的行为。

【讨论】:

    【解决方案2】:

    C 对其所有全局变量使用平面名称空间。与 C++ 不同,C++ 要求链接器注意全局变量的类型(查找名称修饰以获取更多信息),C 将此要求放在程序员身上。

    在同一翻译单元内更改链接时,重新声明具有不同类型的变量是错误的。

    我将使用您的示例并添加一些内容

    static int a;   // internal linkage
    static int b;   // internal linkage
    
    void f()
    {
        float a = 123.25; // this variable shadows static int a
        int b = 321;      // this variable shadows static int b
    
        { // Open a new scope, so the line below is not an illegal re-declaration
            // The declarations below "un-shadow" static a and b
            extern int a; // redeclares "a" from the top, "a" remains internal
            extern int b; // redeclares "b" from the top, "b" remains internal
            a = 42;       // not an unresolved external, it's the top "a"
            b = 52;       // not an unresolved external, it's the top "b"
            printf("%d %d\n", a, b); // static int a, static int b
        }
        printf("%f %d\n", a, b);     // local float a, int b
    }
    

    这个例子打印

    42 52
    123.250000 321
    

    当您跨多个翻译单元更改类型时,C++ 会在链接时捕获它,而 C 会很好地链接,但会产生未定义的行为。

    【讨论】:

    • 我不明白我在哪里尝试用不同类型重新声明同一范围内的变量。我有'static int a'和'extern int a'。
    • @igntec 我在编辑之前不知何故错过了float a。我在您的示例中添加了 cmets 和 printfs 以说明发生了什么。
    • @igntec 当您使用本地变量“隐藏”变量时,类型无关紧要。当您使用externstatic“取消隐藏”它们时,类型确实很重要(它需要匹配顶部的那个,否则会出现编译时错误)。
    • 它有效并且它似乎是顶级'a',但这是否是未定义的行为?由于我们有没有链接的堆栈变量“int b”,“extern int b”应该有外部链接。 C 标准说:如果在翻译单元内,相同的标识符同时出现在内部和外部链接中,则行为未定义。
    • @igntec 根据上面引用的标准部分,它是static int b,而不是int b = 321,因为标准说“如果前面的声明指定了内部或外部链接”。 static int b 指定内部链接,而int b = 321 没有链接。
    猜你喜欢
    • 2017-09-29
    • 2020-12-18
    • 2013-12-08
    • 2011-11-06
    • 1970-01-01
    • 2013-05-10
    • 2021-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多