【问题标题】:Under what circumstances can an extern variable be used in definition?在什么情况下可以在定义中使用外部变量?
【发布时间】:2011-11-09 18:27:23
【问题描述】:

非常抱歉。我不知道我不完整的代码附件会造成如此混乱。我很高兴看到这么多真诚的帮助。

此代码将编译:

int myadd(int, int);
static int main_stat = 5;

int main()
{
    int i, j;
    main_stat = 13;
    j = myadd(-1,7);
    i = main_stat;

    cout << j << i;     //  3  and 13
    return 0;

}

myadd.cpp

extern int main_stat = -3;
int myadd(int x,int y)
{
    int t = main_stat;
    t = x + y;
    y = t +main_stat;
    return y;    // will return 3
}

见我定义和外部链接main_stat。为什么这是合法的?我以为你只能链接而不能定义。

存储是否在 myadd 函数调用的堆栈帧中分配?全局静态分配在堆上,我相信,对吧?


编辑

很抱歉,但我想这次我会缩小我的问题范围:

来自 C++ Primer 4ed

extern 声明可能包含一个初始化器(当组合 成为定义) 仅当它出现在函数之外。

我很清楚单一定义规则。

第一季度。 myadd(int,int) 在调用时使用哪个 main_stat 副本?与主副本相同,但具有不同的值(我可以测试)?还是每个函数都有自己的静态全局副本?

第二季度。这些全局静态变量是否在堆上分配了内存?我知道很多事情要实现,但是堆不是用于静态变量吗?

第三季度。我知道以下两个是有效的

extern int x;    // means int x is defined elsewhere
extern int x = 3;  // declared and defined 

如果我们可以在 myadd 的命名空间中声明一个静态全局变量,为什么还要使用第二个?它是如何让事情变得清晰就像 aschepler 所说的那样?

【问题讨论】:

  • @Mehrdad,嗯,对不起?我不明白:x
  • @mehrdad,我看到你在那儿了。
  • @CppLearner:你不小心把问题的标题部分了
  • 不确定是什么混淆。请展示“另一个文件中的主要功能”的简单示例。
  • @aschepler:他注意到,通常,extern 用于变量以表示定义和存储位于不同的 TU 中。因此,当定义和存储实际上就在同一个 decl/defn 中时,他想知道 extern 在变量上的语义。

标签: c++ variables memory-management extern


【解决方案1】:

所有带有初始化器的变量声明也是定义; 这是一个压倒一切的规则。不管extern。甚至还有 在定义上需要extern 的情况:您只能 使用具有外部链接的变量实例化模板。和 const 变量默认有内部链接,所以你需要 类似:

extern int const i = 42;

如果你想用它来实例化一个template&lt;int const*&gt;

【讨论】:

  • 天哪!!从来不知道这一点。所以 \m/
  • +1: 总结得比我更简洁!还有一些美味的额外花絮。
  • @Nasgul:拿起一本 Josutils 模板书。如果我没记错的话。在第 5 章中介绍,这是对模板的介绍。James:+1 很好地相关。
  • 在C中,一个全局的const默认有外部链接;在 C++ 中,它不会也必须初始化,除非明确声明 extern(第 5.4 节)。 - CPP 斯特鲁普
  • 很好的例子和很好的定义。但是,分配在哪里?我们仍然在 main.cpp 中引用相同的静态 main_stat 吗?谢谢。
【解决方案2】:

以下是声明的定义:

int x;

添加extern 表示“请仅将其作为声明”。

但是当你提供一个值时,是一个定义,所以变量得到extern存储类,你只是碰巧在适当的地方定义了它: p>

extern int x = 3;

链接语义与它们通常用于extern 一样,存储位置与普通定义int x = 3 一样——即在命名空间范围内的该TU 中。 myadd 根本不相关。


这很难“证明”,因为这是一个“没有规则反对它”的案例。

这是最好的报价:

[n3290: 3.1/2]: 声明是一个定义,除非它声明 一个没有指定函数体的函数(8.4),它 包含extern 说明符 (7.1.1) 或链接规范25 (7.5) 既不是初始化程序也不是 函数体,[..]

以及其他一些相关信息:

[n3290: 3.5/2]: 如果一个名称可能表示与另一个范围内的声明引入的名称相同的对象、引用、函数、类型、模板、命名空间或值,则称该名称具有 链接

  • 当名称具有外部链接时,它所表示的实体可以由其他翻译单元的范围或同一翻译单元的其他范围的名称引用。
  • 当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他范围的名称引用。
  • 当名称没有链接时,它所表示的实体不能被其他范围的名称引用。

[n3290: 3.5/12]:块作用域声明的函数名和块作用域声明的变量名extern声明 有联动。如果有一个实体的可见声明 具有相同名称和类型的链接,忽略声明的实体 在最里面的封闭命名空间范围之外,块范围 声明声明同一实体并接收 之前的声明。如果存在不止一个这样的匹配实体, 该程序格式不正确。否则,如果没有找到匹配的实体, 块范围实体接收外部链接。 [..]

【讨论】:

    【解决方案3】:
    extern int main_stat=-3;  
    

    声明并定义main_stat,而:

    extern int main_stat;      
    

    只声明变量main_stat

    您可以拥有任意数量的声明,但您只能拥有一个定义。

    关键字extern,表示外联。没有它main_stat 将是静态的并且具有内部链接,并且您不能使用来自另一个翻译单元的main_stat

    存储是否在 myadd 的堆栈帧中分配?

    不,绝对不在add 的堆栈帧上。
    分配内存的位置由实现定义,但您可以确保对象在整个程序期间都处于活动状态。

    【讨论】:

    • 我认为您可能误解了这个问题。
    • @TomalakGeret'kal:也许……在那种情况下,真正的问题是什么?
    • 我想,“为什么extern 可以用于变量定义,然后会发生什么?存储在哪里?我认为extern 总是意味着存储在别处”
    • 虽然 pf extern 关键字的目的确实是提供外部链接,但上面示例中的变量 main_stat 默认具有外部链接。因此,从链接的角度来看,extern 在这种情况下是多余的。
    • @AndreyT:在 C 中 extern 是多余的,但在 C++ 中不是。
    【解决方案4】:

    这个问题显然源于一些误解。

    有些人认为extern 关键字总是将定义变成非定义声明。这是不正确的。

    关键字extern 只是给出了声明的实体外部链接。它可以应用于声明。它可以应用于定义(记住定义也是声明)。

    因此,说不能定义extern 实体是绝对不正确的。一罐。完全没有问题。

    混淆通常是由于当您将extern 应用于类似的定义时引起的

    int x; // no initializer
    

    那个定义突然变成了一个非定义的声明。这是真的,但这只不过是必须记住的extern 关键字的一次性怪癖。如果您采用类似的定义

    int x = 42;
    

    然后将extern 关键字应用于它仍将保留它作为定义,即在这种情况下没有怪癖。

    【讨论】:

    • 感谢您的帮助。所以让我在这里收集我的理解。对不起,如果我又错了。所以如果我们在 main.cpp 中声明了 int x,然后 extern int x = 30,我们会得到链接器错误,因为没有内存分配,对吧?
    • @CppLearner:如果没有static,您将收到链接器错误,因为您通过为单个变量main_stat 提供两个定义违反了一个定义规则。
    【解决方案5】:

    首先,根据您的评论,包含主函数的文件具有定义static int main_stat = 10;。您应该知道这是 not 与您在包含 myadd 的文件中定义的变量相同,因为作为静态变量,它的范围仅限于该文件。确实,由于具有相同名称的静态变量,main 无法访问您在此文件中定义的变量。

    但这并不意味着任何一个变量都是在堆栈上创建的。两者都是单独的全局变量,只是包含main的文件中的变量main_stat(我将把该文件简称为主文件,这个myadd文件)在任何其他文件中都不可用,而变量您在此处定义的main_stat 可以从包含声明extern main_stat; 的任何文件中访问(注意:没有初始化程序!)。但是,主文件不能包含此声明,因为它会与同名的静态变量冲突。

    请注意,给定初始化器会使您的变量声明成为定义,也就是说,它与省略 extern 相同(但请注意,如果变量被声明为常量,则不能省略 extern,因为常量是默认为静态)。唯一不是定义的全局 extern 声明是那些 with extern,但 without 初始化器。

    【讨论】:

    • celtschk 谢谢。你几乎回答了我问的所有问题。但是有几件事让我感到困惑:(1)所以我们在运行时确实有两个单独的 main_stat ,对吗? (2) myadd 函数中唯一的 main_stat 可以被其他文件访问,对吧? (3) 最后,当extern是一个声明时,我们将在程序的其他地方使用定义,如果我有另一个程序调用myadd,并且在那个程序中我也有extern,我仍然使用主文件的main_stat,对吗?谢谢。
    • (1) 是的。 (2) 是的。 (3) 不可以。在任何情况下,任何其他翻译单元都无法(通过名称)访问 main 中的 main_stat。如果其他翻译单元用extern 声明main_stat,但没有将其定义为外部变量(通过省略extern 或通过提供初始化程序),链接器将抱怨该变量不存在。主文件中的静态文件在其他地方不可见。
    • @CppLearner:我忘记了“at-part”,因此这条评论提醒您,我在上述评论中回答了您的问题(其他评论的评论编辑时间已经过去)。
    【解决方案6】:

    其他人都很好地介绍了这一点,但只是为了在一个地方展示变体:

    int x;                    // #1
    

    是一个声明和定义。 x 的初始值为零。

    int x = 3;                // #2
    

    是一个声明和定义。

    const int cx;             // #3
    

    在 C++ 中是非法的。

    const int cx = 3;         // #4
    

    是一个声明和定义,但cx 如果这是它在翻译单元中的第一个声明,则具有内部链接。

    extern int x;             // #5
    

    是一个声明,但不是一个定义。在程序的其他地方必须有x 的定义。

    extern int x = 3;         // #6
    

    是一个声明和一个定义。 extern 是不必要的,但可以说明问题。

    extern const int cx;      // #7
    

    是一个声明,但不是一个定义。在程序的其他地方必须有cx 的定义。

    extern const int cx = 3;  // #8
    

    是一个声明和一个定义。 extern 是必需的,除非上面的声明已经被看到。

    【讨论】:

    • 谢谢。它是如何让事情变得清晰的?我更新了我的帖子。当 #5 意味着 x 的定义存在于程序中的其他地方时,为什么我们希望 #6 发生?简而言之,在 #6 的情况下,我将有两个不同的 stat 变量副本,命名为 main_stat,一个用于 main 函数,一个用于 myadd,对吗?
    • 我的意思是,虽然#6 的含义与#2 相同,但输入extern 会使变量具有外部链接更加明显。两个变量具有相同名称的情况是static 定义的结果,而不是extern 定义的结果。
    • 谢谢。但是如果我们没有 extern,myadd 就无法访问 main 的静态 main_stat 变量。那么“extern int x = 3”如何等同于#2?如果 myadd 有自己的 main_stat 副本,那么关键字 extern 几乎会被忽略,对吗?谢谢。
    猜你喜欢
    • 1970-01-01
    • 2017-11-30
    • 2014-12-13
    • 1970-01-01
    • 2016-07-03
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    相关资源
    最近更新 更多