上一篇博客给大家解释了“程序运行链接”的概念与意义,并区分了动态链接库与静态链接库。接下来想和大家谈一下C++的内链接与外链接的区别与意义。看完之后,希望你能理解以下几个问题~
1. 为什么不要在头文件中定义具有外部链接的实体?
2. 在头文件中定义具有内部链接的实体有什么劣势?
3. 内部链接与外部链接存在的意义是什么?
首先理解什么是编译单元?
我们知道,其实编译器在编译代码时,只会去编译.cpp格式的源文件,并且预编译器会递归的把.cpp所有#include的头文件都“拷贝”到.cpp文件中去,之后对这个文件再进行编译,生成二进制的.obj文件。那么其实每一个.cpp文件都是一个编译单元。
声明与定义
一个声明将一个名称引入一个作用域,C++中在同一个作用域中可以重复声明,除了类中的成员函数与成员变量的声明。以下都是声明:
Extern int number; //外部引用声明
Typedef int int32; // typedef声明
Class A; //类的前置声明
Using std::cin; //名字空间引用声明
Friend f; //友元声明
Int testFun(); //函数前置声明
定义决定了一个实体在一个作用域的唯一描述,同一作用域不可以重复定义一个实体。以下都是定义:
Int a;
Class Myclass{…};
Myclass ma;
Static int b;
Enum{first, second,third};
Const int m = 2;
Void hello(){…}
什么是内部链接?
如果一个名称对于他的编译单元是局部的,并且在链接时不会与其他的编译单元中同样的名字冲突,那么这个名称就拥有内部链接。这个实体有内部链接,他就不会与其他.cpp文件同名的实体冲突。换个说法,那些编译单元(.cpp)中不能向其他编译单元(.cpp)展示提供其定义的函数、变量就拥有内部链接
那么哪些实体拥有内部链接?
1. 静态(static)全局变量,自由函数,友元函数定义
2. 类的定义
3. 内联函数定义
4. Union共用体定义
5. 名字空间的const常量定义
6. 枚举类型定义
7. 所有的声明(有人将声明归结为无链接)
什么是外部链接?
一个多文件的程序中,一个实体可以在链接时与其他编译单元交互,那么这个实体就拥有外部链接。
换个说法,那些编译单元(.cpp)中能想其他编译单元(.cpp)提供其定义,让其他编译单元(.cpp)使用的函数、变量就拥有外部链接
那么哪些实体拥有外部链接?
1. 类的非内联函数(包括成员函数和静态类成员函数)的定义
2. 类的静态成员变量的定义
3. 名字空间或全局的非静态的自由函数,非静态变量,非友元函数的定义
那么这里总结一下,定义这样的内链接与外链接有什么意义?
所谓链接,就是因为项目工程的不断扩大,写在一个.cpp文件即难以维护,又不好去合作开发。所以去将代码按照比较有条理的,分成多个文件,让其可以独立编译,在最后运行在整合到一起,也就是通过链接再去找到需要的代码。这时候就需要外链接定位到合适的代码。
比如我们定义的全局函数和变量,可以跨模块的链接使用。
有一些名字定义所表示的实体拥有外部链接,这样就意味着他可以跨越编译单元去进行代码的链接。所以,拥有外部链接的实体如果被声明在头文件并且被多个.cpp文件包含,可能就会出现链接冲突错误,因为每个包含这个拥有外部链接实体的.cpp都会分配空间,当多个编译单元链接的时候,连接器就会面对多个相同的名字,无法正常链接到正确的对象。
下面举个例子:(VS2012环境下)
//lesson.h
namespace lesson
{
int test;
}
//lesson.cpp
#include "stdafx.h"
#include "lesson.h"
int _tmain(intargc,_TCHAR*argv[])
{
system("pause");
return0;
}
//test.cpp
#include "lesson.h"
我们就会看到
error LNK2005: "intlesson::test" (?test@lesson@@3HA) 已经在 lesson.obj 中定义C:\Users\user\Documents\Visual Studio 2012\Projects\lesson\lesson\stdafx.obj
这样的错误提示。
而对于拥有内部链接的实体则不会出现这样的情况,因为他不会与其他.cpp的同名实体产生冲突。比如我们将上面的lesson.h改为
//lesson.h
class lesson
{
int test;
}
这样就不会有任何错误,因为类的定义是有内部链接的。
如果在lesson.h里面再定义静态变量,枚举类,进行各种声明等,这些实体由于有内部链接所以仍然是合法的,编译器会认为你想在各个编译单元中都有一个私有的副本。
那么进一步的概括这些内容就是一句话
相同作用域内的声明可以有多个,但是只能定义一次。
先不考虑内链接还是外链接,我们都知道一个{}里面不可能定义两个一模一样的名字。对于一个单独的.cpp文件,我们是知道的,但是对于多个文件,好像就稍微有点晕。其实,这是一个道理,我们的外部链接就是让各个.cpp文件能链接到一起,这样在.cpp文件遇到第一个{}之前,他们的作用域就可以理解为相同的,所以拥有外部链接的实体(全局函数,变量等)出现在第一个{}之前,而且名字相同,那就是出现了定义重复的错误。
我们再看,所有的声明都是有内部链接的,然而他其实可以链接到其他文件,因为他的定义是在其他的编译单元的,所以多个编译单元拥有相同的声明也是合理的。但是,我们知道,这个声明对应的定义肯定只有一个。
最后再给出一个C++编程建议,慎重考虑在头文件中定义有链接的实体
一,如果头文件是像int a=1;这样的定义,被包含在多个.cpp文件后肯定会报出链接错误。
二,如果是想static int a = 2;这样的定义就会在所有包含他的.cpp文件中生成一个副本,如果被大量源文件include的话,就会占据大量的空间,造成内存浪费。