【问题标题】:How do c++ compilers find an extern variable?c++ 编译器如何找到外部变量?
【发布时间】:2020-01-07 08:01:16
【问题描述】:

我用g++和clang++编译这个程序。有区别:
g++ 打印 1,但 clang++ 打印 2。
好像
g++:外部变量定义在最短范围内。
clang++:extern 变量定义在最短的全局范围内。

C++ 规范是否对此有任何规范?

main.cpp

#include <iostream>
static int i;
static int *p = &i;

int main() {
  int i;
  {
    extern int i;
    i = 1;
    *p = 2;
    std::cout << i << std::endl;
  }
}

其他.cpp

int i;

版本:g++:7.4.0/ clang++:10.0.0
编译:$(CXX) main.cpp other.cpp -o extern.exe

【问题讨论】:

  • 编译器不会对 extern 做任何事情,只是将它们标记为具有外部引用的变量,链接器试图解析所有编译目标文件之间的链接。
  • 一个极好的(如果奇怪的话)问题!在MSVCclang-cl(都给出2)中玩弄你的代码,似乎extern int i 被两者完全忽略:即使我没有链接到other.cpp 文件,程序构建并运行。
  • @SPlatten 据推测,由于链接器不需要“解析”对i 的引用,它不会尝试。
  • 相关老挂起的GCC bug可以在here和对应的open Clang bughere找到

标签: c++ language-lawyer c++17


【解决方案1】:

[basic.link/7] 应该是标准的相关部分。在当前的草案中,它说:

在块作用域中声明的函数名和在块作用域extern声明中声明的变量名有链接。如果将这样的声明附加到命名模块,则程序格式错误。如果存在具有链接的实体的可见声明,则忽略在最里面的封闭命名空间范围之外声明的实体,这样如果两个声明出现在同一个声明区域中,则块范围声明将是(可能格式错误的)重新声明,则块范围声明声明同一实体并接收前一个声明的链接。如果存在不止一个这样的匹配实体,则程序是非良构的。否则,如果没有找到匹配的实体,则块范围实体接收外部链接。 如果在一个翻译单元中,同一个实体被声明为具有内部和外部链接,则程序是错误的。

请注意,后面的示例几乎完全符合您的情况:

static void f();
extern "C" void h();
static int i = 0;               // #1
void g() {
  extern void f();              // internal linkage
  extern void h();              // C language linkage
  int i;                        // #2: i has no linkage
  {
    extern void f();            // internal linkage
    extern int i;               // #3: external linkage, ill-formed
  }
}

所以,程序应该是格式错误的。解释在例子下面:

如果没有第 2 行的声明,第 3 行的声明将链接到第 1 行的声明。然而,因为带有内部链接的声明是隐藏的,所以 #3 被赋予了外部链接,从而使程序格式不正确。

【讨论】:

  • 示例中的程序格式错误,因为在任何地方都没有带有外部链接定义的 i。 OP 的示例并非如此。
  • @n.'pronouns'm。但该规则适用于翻译单元:如果在翻译单元内,同一个实体被声明为具有内部和外部链接,则程序格式错误。.
  • 答案仅适用于 C++17 及更高版本,请参阅CWG issue 426 的解析。在我看来,GCC 在更改之前是正确的。
  • 好吧,我好像在阅读上一版的标准。
【解决方案2】:

Clang 给出了正确的结果。即使按照当前标准的规则,程序也不应该是格式错误的。注意强调的措辞:

如果在一个翻译单元中,同一个实体被声明为具有内部和外部链接,则程序是非良构的。

#3 声明的实体和#1 声明的实体不是同一个实体,因为以下规则:

如果两个名称相同且在不同作用域中声明,则应表示相同的变量、函数、类型、模板或命名空间

  • 两个名称都有外部链接,否则两个名称都有内部链接并在同一个翻译单元中声明;和
  • [...]

它们不是,一个有internal linkage,另一个有external linkage,所以它们不表示相同实体,因此代码不违反[basic.link#6 ]。此外,[basic.link#6] 后面的示例仍然是对变量 i 的错误解释。

P1787 已经阐明了这个例子。它说:

static void f();
extern "C" void h();
static int i = 0;               // #1
void gq() {
  extern void f();              // internal linkage
  extern void g();              // ::g, external linkage
  extern void h();              // C language linkage
  int i;                        // #2: i has no linkage
  {
    extern void f();            // internal linkage
    extern int i;               // #3: <del>ex</del>internal linkage
  }
}

Without 即使第 2 行的声明隐藏了第 1 行的声明,第 3 行的声明 将与仍然重新声明连接es at line #1. 因为声明with和received内部链接是隐藏的,但是#3是隐藏的给定外部链接,使程序格式错误

这意味着,在您的示例中,由声明 extern int i 引入的变量 i 将与由 static int i 声明的变量 i 链接。所以,打印2 是正确的行为。

【讨论】:

  • 相关 CWG 页面 P1787 [basic.link] 和 +1 更新。
  • 如果实体通过链接延迟而不相同,如何违反第一条规则? :D
  • @LanguageLawyer 你提到的第一条规则是什么?
  • 如果在一个翻译单元中,同一个实体被声明为具有内部和外部链接,则程序是不正确的。
  • @LanguageLawyer 我没有说代码违反了该规则。因为它们不是每个basic.link#9相同 实体,issue 426 wg21.cmeerw.net/cwg/msg1413 类似。也许你的意思是我不应该说violate 而不是doesn't satisfy that rule
猜你喜欢
  • 2014-04-29
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多