首先要知道一些概念:
编译:将以文本形式存在的源代码翻译为机器语言形式的目标文件的过程。
编译单元:对于C++来说,【每一个cpp文件就是一个编译单元】。 各个编译单元之间是互相不可知的。
目标文件:由编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据,以及 一些其他的信息 。
符号: 在C/C++中,每一个变量和函数都有自己的符号。例如变量n的符号就是“n”。函数的符号要更加复杂,它需要结合函数名及其参数和调用惯例等,得到一个唯一的字符串。f的符号可能就是"_f"(根据不同编译器可以有变化)。
程序形成的过程:
.cpp -编译-> .o -链接-> .exe
编译出的.O文件:
- 将代码转换成机器语言
- 提供两个表
- 导出符号表 (告诉连接器,自己提供什么)
- 未解决符号表 (自己需要什么)
- 一个地址重定向表 (安排每个编译单元的地址偏移量,寻址是 基地址+偏移量)
链接性:
外部链接 extern: 告诉编译器,这个符号在别的编译单元里定义(就是把它放到未解决符号表中去)。
内部链接 static: 表示不导出这个符号 (就是不让别人看见这个符号)
外部链接的利弊:外部链接的符号,可以在整个程序范围内使用(因为导出了符号)。但是同时要求其他的编译单元不能导出相同的符号(不然就是duplicated external simbols)
内部链接的利弊:内部链接的符号,不能在别的编译单元内使用。但是不同的编译单元可以拥有同样名称的内部链接符号。
默认: 函数和变量 有外部链接性 ; const变量 有内部链接性 (可以通过添加extern和static改变链接属性)
几个很不错的问题:
- 为什么头文件里一般只可以有声明不能有定义:
头文件可以被多个编译单元包含,如果头文件里有定义,那么每个包含这个头文件的编译单元就都会对同一个符号进行定义,如果该符号为外部链接,则会导致duplicated external simbols。因此如果头文件里要定义,必须保证定义的符号只能具有内部链接。 (声明只是告诉编译器,我要用到一个 xxx ,但是xxx在不在这里就不管了。到定义的时候,再去分配内存或者去别的.o文件里找 )
- 为什么常量默认为内部链接,而变量不是:
这就是为了能够在头文件里如const int n = 0这样的定义常量。由于常量是只读的,因此即使每个编译单元都拥有一份定义也没有关系。如果一个定义于头文件里的变量拥有内部链接,那么如果出现多个编译单元都定义该变量,则其中一个编译单元对该变量进行修改,不会影响其他单元的同一变量,会产生意想不到的后果。
- 为什么函数默认是外部链接:
虽然函数是只读的,但是和变量不同,函数在代码编写的时候非常容易变化。(如果函数默认具有内部链接,则人们会倾向于把函数定义在头文件里,那么一旦函数被修改,所有包含了该头文件的编译单元都要被重新编译。另外,函数里定义的静态局部变量也将被定义在头文件里。)
- 为什么类的静态变量不可以就地初始化 :所谓就地初始化就是类似于这样的情况:
class A
{
static char msg[] = "aha";
};
不允许这样做得原因是,由于class的声明通常是在头文件里,如果允许这样做,其实就相当于在头文件里定义了一个非const变量。
作为例外,int/char等可以进行就地初始化,是因为这些变量可以直接被优化为立即数,就和宏一样。
在C++里,头文件定义一个const对象会怎么样:
一般不会怎么样,这个和C里的在头文件里定义const int一样,每一个包含了这个头文件的编译单元都会定义这个对象。但由于该对象是const的,所以没什么影响。但是:有2种情况可能破坏这个局面:
1。如果涉及到对这个const对象取地址并且依赖于这个地址的唯一性,那么在不同的编译单元里,取到的地址可以不同。(但一般很少这么做)
2。如果这个对象具有mutable的变量,某个编译单元对其进行修改,则同样不会影响到别的编译单元。
- 内联函数:
C++里的内联函数由于类似于一个宏,因此不存在链接属性问题。
- 为什么公共使用的内联函数要定义于头文件里 :
因为编译时编译单元之间互相不知道,如果内联函数被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因此无法对函数进行展开。所以说如果内联函数定义于.cpp文件里,那么就只有这个cpp文件可以是用这个函数。
- 头文件里内联函数被拒绝会怎样:
如果定义于头文件里的内联函数被拒绝,那么编译器会自动在每个包含了该头文件的编译单元里定义这个函数并且不导出符号。
如果被拒绝的内联函数里定义了静态局部变量,这个变量会被定义于何处:
早期的编译器会在每个编译单元里定义一个,并因此产生错误的结果,较新的编译器会解决这个问题,手段未知。
- 为什么export关键字没人实现:
export要求编译器跨编译单元查找函数定义,使得编译器实现非常困难。
摘自:http://www.cppblog.com/shifan3/archive/2007/01/05/17325.html