一、前言

最近在看《大规模C++程序设计》一书,看第一章关于内部链接和外部链接这部分时,有点不太明白。通过书本理解和网上查阅文献,在此记录一下自己对这部分知识点的理解。

首先,提几个问题:

  • 什么是内部链接,什么是外部链接?
  • 为什么不要在头文件中定义具有外部链接的实体?
  • 在头文件中定义具有内部链接的实体有什么劣势?
  • 内部链接和外部链接存在的意义是什么?

二、编译单元

理解C++的【内部链接】和【外部链接】_c++

上图简单的表述了程序的编译、链接过程。其实编译器在编译代码时,只会去编译.cpp格式的源文件,并且预编译器会递归的把.cpp中所有#include的头文件都“拷贝”到.cpp文件中去,将#include替换成对应头文件中的内容,形成一个完整的.cpp文件,之后再对这个文件进行编译,生成二进制的.obj文件,所以其实每个.cpp文件就是一个编译单元。

每个编译单元由编译器独立编译,编译完成后,链接器会将编译后的编译单元合并到单个程序中!!!


三、声明和定义

3.1、声明

描述:一个声明将一个名称引入一个作用域

声明可以理解为,我现在声称有这个东西,但是这个东西具体是啥样,我不清楚。

  • C++中在同一个作用域中可以重复声明,除了类中的成员函数与成员变量的声明

以下都是声明:

extern int number; 	//外部引用声明

typedef int  int32; // typedef声明

Class A;          	//类的前置声明

Using std::cin;   	//名字空间引用声明

friend f;         	//友元声明

int testFun();    	//函数前置声明

3.2、定义

描述:定义决定了一个实体在一个作用域的唯一描述

定义可以这么理解,声明是声称有这个东西,但是具体是啥样还不清楚;定义就清清楚楚、明明白白的告诉你,这个东西是啥样。

  • 同一作用域不可以重复定义一个实体

以下都是声明:

int a;						//变量定义

Class Myclass{…};			//类定义

Myclass ma;					//类对象定义

static int b;				//静态变量定义

enum{first, second,third};	//枚举类型定义

const int m = 2;			//常量定义

void hello(){…}				//函数定义

四、内部链接

描述:如果一个名称对于它的编译单元是局部的,并且在链接时不会与其它的编译单元中同样的名字冲突,那么这个名称就拥有内部链接。这个实体有内部链接,它就不会与其他.cpp文件同名的实体冲突。

以下实体拥有内部链接:

  • 静态全局变量定义
  • 自由函数(非类成员函数)定义
  • 友元函数定义
  • 类的定义
  • 内联函数定义
  • union共用体定义
  • 名字空间的const常量定义
  • enum枚举类型定义
  • 所有的声明

五、外部链接

描述:一个多文件的程序中,一个实体可以在链接时与其它编译单元交互,那么这个实体就拥有外部链接。也就是说,如果该编译单元能向其它编译单元提供其定义,供其它编译单元使用的函数、变量就拥有外部链接。

以下实体拥有外部链接:

  • 类的非内联函数的定义(包括:成员函数和静态类成员函数)
  • 类的静态成员变量的定义
  • 名字空间或全局的非静态的自由函数、非静态变量、非友元函数的定义

六、内部链接和外部链接的意义

链接:就是因为项目工程的不断扩大,写在一个.cpp文件即难以维护,又不好去合作开发。所以去将代码按照比较有条理的,分成多个文件,让其可以独立编译,最后运行时再整合到一起,也就是通过链接去找到需要的代码。这时候就需要外链接定位到合适的代码。

比如:我们定义的全局函数和变量,可以跨模块的链接使用。

有一些名字定义所表示的实体拥有外部链接,这样就意味着他可以跨越编译单元去进行代码的链接。所以,拥有外部链接的实体如果被声明在头文件,并且被多个.cpp文件包含,可能就会出现链接冲突错误。因为每个包含这个拥有外部链接实体的.cpp都会分配空间,当多个编译单元链接的时候,连接器就会面对多个相同的名字,无法正常链接到正确的对象。

例如:我们在一个头文件中定义了一个名字空间,名字空间的定义显然具有外部链接

#ifndef LESSON_H
#define LESSON_H


namespace lesson_1 {
    int test;
}

class lesson
{
public:
    lesson();
};

#endif // LESSON_H
#include "lesson.h"

lesson::lesson()
{

}

然后我们在main.cpp中引入:#include "lesson.h"

#include <iostream>

#include "lesson.h"

using namespace std;

int main()
{
    cout << "Hello World!" << endl;
    return 0;
}

一运行就报错:

理解C++的【内部链接】和【外部链接】_#include_02


因为定义在lesson.h中的namespace lesson具有外部链接,lesson.cppmain.cpp#include<lesson.h>,在链接时,这两个编译单元,拥有同名的外部链接,程序就不知道怎么去把编译单元拼接起来,所以报错:重复定义lesson_1::test


七、建议

把有内部连接的定义限制在单个的编译单元中,不能影响其它的编译单元,除非它放在一个头文件中。这样的定义可以存在于.c文件的文件作用域内,不会影响全局(符号)名称空间。

有外部连接的定义,在连接时可以用来解析其它编译单元中未定义的符号。把这样的定义放在头文件中几乎肯定是一个编程错误,因为头文件极大概率会被多个文件引用。