第15章:source files and programs

一:源文件要变成计算机可执行文件,需要经过编译和链接两个步骤。编译时,编译器一次只能处理一个源文件,当源文件经过编译变成目标文件后,链接器一次处理多个目标文件,对这些目标文件进行组织,使其变成计算机可执行文件。

二:当链接器链接多个目标文件时,如果一个名字在一个目标文件中被定义,但是可以在其它目标文件中使用,那么就称其有外部链接(external linkage);如果这个名字只能被其定义所在的目标文件使用,那么就称其有内部链接(internal linkage);当然有些名字,链接器不会去查看,比如局部变量的名字,那么就称其为没有链接(no linkage)。虽然具有内部链接的名字和没有链接的名字都只能被其定义所在的文件使用,但是链接器会去查看具有内部链接的名字,而不会去查看没有链接的名字。

三:1:在下面的一些情况下,名字具有外部链接。比如 在全局域,命名空间作用域定义的变量名字,如int x, string s中的名字x 和s;定义的普通函数,模板函数的名字(constexpr函数除外);定义的普通类,模板类的名字。

同时一定要注意的是,在全局域和命名空间作用域中如果一个变量被定义但是没有初始化,这个变量会被默认初始化,比如在全局域或命名空间作用域中的int x语句,或许我们是想声明x,但是事实上这个变量会被赋予初值0,变量x被定义了而不是如我们想的,只是声明而已,要想在全局域或命名空间域中声明,要加上关键字extern,比如extern int x这时候就只是声明x,不是定义。

2:在下面的一些情况下,名字具有内部链接,比如在命名空间作用域或全局域中用static声明的变量,比如static int x1=1;还有就是const,constexpr对象名字,以及 type alias,这些都只有内部链接。但是如果我们想要使const对象名字具有外部链接,那么当我们定义const对象时要在其前面加上extern关键字,比如extern const char c=’a’,这个名字c具有外部链接。

四:我们都听说过”一个定义规则”,说的是一个对象只能被定义一次,但是可以被声明多次。因此对于那些具有外部链接的名字来说,如果他们在两个将要被链接在一起的源文件中都被相同的定义了,那么链接器会报错,比如我们在两个源文件中都定义了一个一模一样的普通函数或者是变量,这时候链接器会报错。

但是我们经常会在头文件中定义一个类,然后被多个将要链接到一起的源文件#include,还有就是内联函数的使用要求其在不同的源文件要被相同的定义, 这两种情况明显违反了上述所说的”只能被定义一次”的规则。那么这该如何解释呢?可以如下来解释:

* 当我们对变量和普通函数应用”一个定义”规则时,我们要求它们只能定义一次,不能在多个源文件中被相同定义。当我们对类,模板类,内联(inline)函数,模板函数等应用”一个定义”规则时,它们可以在多个源文件中被相同定义,但是此时链接器会把这些定义看成是一个定义,这刚好也符合”一个定义”规则(one-definition rule),当然如果他们在同一个源文件中被定义了多次,那么这时候就违反了一个定义规则。*

有了上述的解释,我们就能明白以下一些情况:
1):内联函数可以在多个源文件中被相同定义,通常情况我们会把内联函数的使用放在一个头文件中。
2):我们可以把非模板类的定义放在一个头文件中,同时可以被多个源文件#include。并且由于普通函数不能在两个源文件中被相同定义,因此如果非模板类的成员函数在类体中给出了定义,那么我们就认为这个成员函数是内联函数。但是一般情况下我们会在类体中给出成员函数声明,在另外一个.cc文件中给出成员函数定义。
3):我们也可以把模板类的定义放在一个头文件中,同时可以被多个源文件#include。我们在模板类体中声明模板类成员函数,在另外一个.cc文件中给出该模板成员的定义,但是然后我们会在定义模板类的头文件末尾include该.cc文件。之所以可以这样做是因为模板函数可以在多个源文件中被相同定义,所以当定义模板类的头文件被多个源文件include的时候,不会出现链接错误。

五:我们会经常在头文件中写一些声明或者定义,比如类的定义,函数的声明等等,这时候我们一定要注意当我们在定义(不是声明)一个具有外部链接的名字的时候,一定要确保它不能违反”一个定义”规则,因为我们不知道这个头文件是否会被多个将要被链接在一起的源文件include,比如我们不能在头文件中给出普通函数定义,或变量的定义(像 int a这种)。我们可以在头文件中给出类,模板类,内联(inline)函数,模板函数的定义,正如上面说的,虽然它们的名字具有外部链接,但是并不违反”一个定义”规则。当然我们可以在头文件给出具有内部链接名字的定义,比如const float pi=3.1415,因为这些名字不会被其它源文件访问。一个头文件该包含什么,不该包含什么可以参看15.2.2那一小节。

六:在开发大型程序时,我么应该把逻辑功能相同的接口(interface)放在一个头文件中,同时在对应的.cc文件中给出这些接口的定义,不提倡把不具有相同逻辑功能的接口放在一个头文件中。并且在开发大型项目时,多个头文件被#include是不可避免的,事实上几百个头文件(不包括标准库头文件)被#include是正常的,但是如果几千个头文件被#include,那么这些头文件的管理就变得很麻烦了。因此只在必要的时候才#include头文件这种做法是被提倡的,这不仅可以减少头文件被#include的数目从而缩短编译时间,这也可以避免带来不必要的声明或者定义,而过多的声明或定义或许会造成想象不到的麻烦。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值