繁体   English   中英

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

[英]Usage of extern keyword and multiple translation units

我正在阅读Scott Meyer的Effective C ++的第4项,他试图显示一个示例,其中跨不同的翻译单元使用静态非本地对象。 他强调了一个问题,即一个翻译单元中使用的对象在使用之前不知道是否已在另一翻译单元中对其进行了初始化。 第三版的第30页,以防有人复制。

这个例子是这样的:

一个文件代表一个库:

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

extern FileSystem tfs;

并在客户端文件中:

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

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

因此,我的两个问题是:

1)如果客户端代码需要使用tfs ,则将包含某种include语句。 因此,确定此代码完全在一个翻译单元中吗? 我看不到如何引用不同翻译单元中的代码? 当然,程序始终是一个翻译单元吗?

2)如果客户端代码包含FileSystem.h,则行extern FileSystem tfs; 足以让客户端代码调用tfs(我很欣赏初始化可能存在运行时问题,我只是在谈论编译时范围)?

编辑到Q1

该书说这两段代码在单独的翻译单元中。 知道客户端代码在单独的翻译单元中,客户端代码如何使用变量tfs

这是一个简化示例,说明跨多个TU进行初始化可能会出现问题。

gadget.h:

struct Foo;

extern Foo gadget;

gadget.cpp:

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

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

client.cpp:

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

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

    return bar(x * 2);
}

问题在于,不能保证在do_something()引用该gadget对象之前,该gadget对象已被初始化。 仅保证一个TU中的初始化程序在调用该TU中的功能之前完成。

(解决方案是用Foo & gadget();替换extern Foo gadget; ;,在gadget.cpp中将其实现为{ static Foo impl; return impl; }并使用gadget().frumple() 。)

1)如果客户端代码需要使用tfs,则将包含某种include语句。 因此,确定此代码完全在一个翻译单元中吗? 我看不到如何引用不同翻译单元中的代码? 当然,程序始终是一个翻译单元吗?

预处理后,翻译单元(大致)是单个.cpp文件。 编译单个转换单元后,您将获得一个模块对象(通常具有扩展名.o.obj ); 在编译完所有TU之后,它们将由链接器链接在一起以形成最终的可执行文件。 这通常被IDE(甚至包括在命令行上接受多个输入文件的编译器)所隐藏,但至关重要的是要了解构建C ++程序至少要经过三遍:预编译,编译和链接。

#include语句将包含类的声明和extern声明,告诉当前的翻译单元以这种方式创建了FileSystem类,并且在某些翻译单元中,存在类型为FileSystem的变量tfs

2)如果客户端代码包含FileSystem.h,则行extern FileSystem tfs; 客户端代码足以调用tfs

是的, extern声明告诉编译器在某个TU中有一个这样定义的变量。 编译器会在对象模块中放置一个占位符,链接器在将各个对象模块捆绑在一起时,将使用实际tfs变量(在其他转换单元中定义)的地址对其进行修复。

请记住,当您编写extern您只是在声明一个变量(即,您告诉编译器“信任我,某处有这个东西”),而忽略它时,您既在声明它又定义了它(“有这个东西,然后您必须在这里创建它”)。

函数之间的区别可能更清楚:编写原型时,您声明的是函数(“某处有一个x带有此类参数并返回此类型的函数”),而实际上编写函数(带有函数主体)时,您就是定义它(“此函数实际上是在做什么”),并且,如果您以前没有声明过,它也算作一个声明。


有关实际使用/管理多个TU的方式,您可以看一下我的这个答案

这是来自标准C ++ 03的示例(我添加了ahbh标头):

[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();
}

由实现定义,是在输入main之前初始化a或b还是将初始化延迟到在main中首次使用a之前。 特别是,如果在输入main之前对a进行了初始化,则不能保证b会在a的初始化使用它之前(即在调用A :: A之前)被初始化。 但是,如果a在main的第一条语句之后的某个时刻被初始化,则b将在其在A :: A中使用之前被初始化。

暂无
暂无

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

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