简体   繁体   English

使用extern关键字和多个翻译单元

[英]Usage of extern keyword and multiple translation units

I am reading Item 4 of Scott Meyer's Effective C++ where he is trying to show an example where a static non-local object is used across different translation units. 我正在阅读Scott Meyer的Effective C ++的第4项,他试图显示一个示例,其中跨不同的翻译单元使用静态非本地对象。 He is highlighting the problem whereby the object used in one translation unit does not know if it has been initialised in the other one prior to usage. 他强调了一个问题,即一个翻译单元中使用的对象在使用之前不知道是否已在另一翻译单元中对其进行了初始化。 Its page 30 in the third edition in case anyone has a copy. 第三版的第30页,以防有人复制。

The example is such: 这个例子是这样的:

One file represents a library: 一个文件代表一个库:

class FileSystem{
    public:
        std::size_t numDisks() const;
    ....
};

extern FileSystem tfs;

and in a client file: 并在客户端文件中:

class Directory {
    public:
        Directory(some_params);
    ....
};

Directory::Directory(some_params)
{
    ...
    std::size_t disks = tfs.numDisks();
    ...
}

My two questions are thus: 因此,我的两个问题是:

1) If the client code needs to use tfs , then there will be some sort of include statement. 1)如果客户端代码需要使用tfs ,则将包含某种include语句。 Therefore surely this code is all in one translation unit? 因此,确定此代码完全在一个翻译单元中吗? I do not see how you could refer to code which is in a different translation unit? 我看不到如何引用不同翻译单元中的代码? Surely a program is always one translation unit? 当然,程序始终是一个翻译单元吗?

2) If the client code included FileSystem.h would the line extern FileSystem tfs; 2)如果客户端代码包含FileSystem.h,则行extern FileSystem tfs; be sufficient for the client code to call tfs (I appreciate there could be a run-time issue with initialisation, I am just talking about compile-time scope)? 足以让客户端代码调用tfs(我很欣赏初始化可能存在运行时问题,我只是在谈论编译时范围)?

EDIT to Q1 编辑到Q1

The book says these two pieces of code are in separate translation units. 该书说这两段代码在单独的翻译单元中。 How could the client code use the variable tfs , knowing they're in separate translation units?? 知道客户端代码在单独的翻译单元中,客户端代码如何使用变量tfs

Here's a simplified example of how initialization across multiple TUs can be problematic. 这是一个简化示例,说明跨多个TU进行初始化可能会出现问题。

gadget.h: gadget.h:

struct Foo;

extern Foo gadget;

gadget.cpp: gadget.cpp:

#include <foo.h>
#include <gadget.h>

Foo gadget(true, Blue, 'x');    // initialized here

client.cpp: client.cpp:

#include <foo.h>
#include <gadget.h>

int do_something()
{
    int x = gadget.frumple();   // problem!

    return bar(x * 2);
}

The problem is that it is not guaranteed that the gadget object will have been initialized by the time that do_something() refers to it. 问题在于,不能保证在do_something()引用该gadget对象之前,该gadget对象已被初始化。 It is only guaranteed that initializers within one TU are completed before a function in that TU is called. 仅保证一个TU中的初始化程序在调用该TU中的功能之前完成。

(The solution is to replace extern Foo gadget; with Foo & gadget(); , implement that in gadget.cpp as { static Foo impl; return impl; } and use gadget().frumple() .) (解决方案是用Foo & gadget();替换extern Foo gadget; ;,在gadget.cpp中将其实现为{ static Foo impl; return impl; }并使用gadget().frumple() 。)

1) If the client code needs to use tfs, then there will be some sort of include statement. 1)如果客户端代码需要使用tfs,则将包含某种include语句。 Therefore surely this code is all in one translation unit? 因此,确定此代码完全在一个翻译单元中吗? I do not see how you could refer to code which is in a different translation unit? 我看不到如何引用不同翻译单元中的代码? Surely a program is always one translation unit? 当然,程序始终是一个翻译单元吗?

A translation unit is (roughly) a single .cpp file after preprocessing. 预处理后,翻译单元(大致)是单个.cpp文件。 After you compile a single translation unit you get a module object (which typically have extension .o or .obj ); 编译单个转换单元后,您将获得一个模块对象(通常具有扩展名.o.obj ); after all TUs have been compiled, they are linked together by the linker to form the final executable. 在编译完所有TU之后,它们将由链接器链接在一起以形成最终的可执行文件。 This is often hid by IDEs (and even by the compilers accepting multiple input files on the command line), but it's crucial to understand that building a C++ program is made in (at least) three passes: precompilation, compilation and linking. 这通常被IDE(甚至包括在命令行上接受多个输入文件的编译器)所隐藏,但至关重要的是要了解构建C ++程序至少要经过三遍:预编译,编译和链接。

The #include statement will include the declaration of the class and the extern declaration, telling to the current translation unit that the class FileSystem is made that way and that, in some translation unit, there's a variable tfs of type FileSystem . #include语句将包含类的声明和extern声明,告诉当前的翻译单元以这种方式创建了FileSystem类,并且在某些翻译单元中,存在类型为FileSystem的变量tfs

2) If the client code included FileSystem.h would the line extern FileSystem tfs; 2)如果客户端代码包含FileSystem.h,则行extern FileSystem tfs; be sufficient for the client code to call tfs 客户端代码足以调用tfs

Yes, the extern declaration tells the compiler that in some TU there's a variable defined like that; 是的, extern声明告诉编译器在某个TU中有一个这样定义的变量。 the compiler puts a placeholder for it in the object module and the linker, when tying together the various object modules, will fix it with the address of the actual tfs variable ( defined in some other translation unit). 编译器会在对象模块中放置一个占位符,链接器在将各个对象模块捆绑在一起时,将使用实际tfs变量(在其他转换单元中定义)的地址对其进行修复。

Keep in mind that when you write extern you are only declaring a variable (ie you are telling the compiler "trust me, there's this thing somewhere"), when you omit it you are both declaring it and defining it ("there's this thing and you have to create it here"). 请记住,当您编写extern您只是在声明一个变量(即,您告诉编译器“信任我,某处有这个东西”),而忽略它时,您既在声明它又定义了它(“有这个东西,然后您必须在这里创建它”)。

The distinction maybe is clearer with functions: when you write a prototype you are declaring a function ("somewhere there's a function x that takes such parameters and returns this type"), when you actually write the function (with the function body) you are defining it ("this is what this function actually does"), and, if you haven't declared it before, it counts also as a declaration. 函数之间的区别可能更清楚:编写原型时,您声明的是函数(“某处有一个x带有此类参数并返回此类型的函数”),而实际上编写函数(带有函数主体)时,您就是定义它(“此函数实际上是在做什么”),并且,如果您以前没有声明过,它也算作一个声明。


For how multiple TUs are actually used/managed, you can have a look at this answer of mine . 有关实际使用/管理多个TU的方式,您可以看一下我的这个答案

Here's the example from the Standard C++03 (I've added the ah and bh headers): 这是来自标准C ++ 03的示例(我添加了ahbh标头):

[basic.start.init]/3 [basic.start.init] / 3

// a.h
struct A { A(); Use(){} };

// b.h
struct B { Use(){} };

// – File 1 –
#include "a.h"
#include "b.h"
B b;
A::A(){
    b.Use();
}

// – File 2 –
#include "a.h"
A a;

// – File 3 –
#include "a.h"
#include "b.h"
extern A a;
extern B b;
int main() {
    a.Use();
    b.Use();
}

It is implementation-defined whether either a or b is initialized before main is entered or whether the initializations are delayed until a is first used in main. 由实现定义,是在输入main之前初始化a或b还是将初始化延迟到在main中首次使用a之前。 In particular, if a is initialized before main is entered, it is not guaranteed that b will be initialized before it is used by the initialization of a, that is, before A::A is called. 特别是,如果在输入main之前对a进行了初始化,则不能保证b会在a的初始化使用它之前(即在调用A :: A之前)被初始化。 If, however, a is initialized at some point after the first statement of main, b will be initialized prior to its use in A::A. 但是,如果a在main的第一条语句之后的某个时刻被初始化,则b将在其在A :: A中使用之前被初始化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM