C++程序编译链接

来源:互联网 发布:日本留学费用 知乎 编辑:程序博客网 时间:2024/05/24 06:14

C++程序编译过程


一般来说,我们可以把C++程序编译过程分为以下三步

编译预处理

主要进行源码级别上的操作,预处理器执行源码中的预处理命令(以‘#’号开头的语句),其中预处理命令可以分为以下几类

a. 宏定义命令[ #define 宏名 替换内容 、#undef  宏名]:进行代码替换, 凡是遇到标识符为宏名的都直接用“替换内容”进行替换。

b.条件编译命令[ #if ...  、 #else 、 #elseif ...、 #ifdef ... 、#ifndef ... 、#endif 、#error messageStr]: 根据条件判断来选取代码块作为编译程序输入。

c.包含文件命令[ #include<iostream>]: 用文件内容替换这条命令。

d.预定义宏名[ _LINE_、_DATE_、_TIME_、_FILE_]

e.预编译模块[[#pragma]:一般被用作编译器的拓展,用来设置编译器状态或指示编译器完成特定动作,比如VC++的[#pragma once]用于防止头文件被重复包含,[#pragma omp parallel for]用于VC++对OMP加速的支持。

e.特殊符号,比如#line, 如果在源码中出现,将会被解释成当前行号。

编译 优化 汇编

C++程序的编译过程是分模块进行的,每一个模块独立编译,生成相应的.obj或.o文件,一般情况下,每一个.c或.cpp文件作为一个独立的模块进行编译。需要注意的是,.h文件是不会被编译的。这个编译过程会检查变量和函数是否有被声明,进行词法检查,语法检查、、、,生成汇编代码,优化汇编代码,最后经过汇编程序翻译成目标机器代码,生成.obj或.o文件。

***

因为每一个模块是独立编译的,所以对于定义在其它模块的函数或变量的使用都是未定义的,编译时检查到有声明只是告诉模块--这个函数或变量定义在其它模块中了,当然这样编译后生成的文件是无法直接执行的。因此,我们需要一个过程将各个编译后的模块合理组合起来,并将各个模块中对其它模块的函数调用或变量引用设置到正确的地址,这个过程就叫做链接。链接完各个.obj或.o文件后才能生成一个可执行文件。


***

链接过程需要编译时收集每一个模块的相关信息才能完成,编译每一个模块的同时还要生成三个表供链接过程使用:

(1 )导出符号表:提供了本编译单元具有定义,并且愿意提供给其他编译单元使用的符号及其地址。

(2)未解决符号表:提供了所有在该编译单元里引用但是定义并不在本编译单元里的符号及其出现的地址。

(3)地址重定向表:因为每个模块的逻辑地址都是从0开始的,这样的地址是相对的,在链接成可执行文件之前需要知道每个.obj文件在可执行文件中的起始位置,这样(相应.obj的起始位置+导出符号表中导出符号对应的位置)才是一个正确的的地址。这样,我们就需要告诉链接器,哪些地址是需要重定位的。这样,链接器在进行链接时,首先会决定每一个.obj文件在可执行文件中的起始位置,然后查看模块的地址重定向表,在需要重定向的位置上加上相应模块实际在可执行文件中的起始位置,这样的地址才是正确的地址。


***

对于每个模块,一般而言:

(1)用extern关键字修饰的符号是外部链接,它告诉编译器,这个符号定义在其它模块,应该把这个符号放入未解决符号表。

(2)用static关键字修饰的符号都是内部链接符号,也就是说这些符号仅仅在模块内部可见,不会提供给其它模块引用,也就不会被放进导出符号表。

(3)默认情况下,const常量是内部链接,不会被加到导出符号表中。

(4 ) 默认情况下,函数和全局变量都是外部链接符号,这些符号会被放入模块的导出符号表,以供其它模块使用,但是可以用static关键字修饰,把它变成内部链接,这样就不会被放进导出符号表。


***?为什么函数默认是外部链接?

答:

如果函数默认是内部链接,那么大家会倾向于把函数连同其定义都放入头文件中。然而,函数是多变的,可能会经常修改,这样一来,所以包含它的模块都需要被重新编译,很麻烦。另外一方面,如果函数中定义了静态变量,这样每一个包含该函数的模块都会有一个静态变量(因为假设是默认内部链接),导致不一致。


***?为什么const常量默认是内部链接而变量(全局)默认是外部链接?

答:

因为它是常量,初始化后就不能改变,这样即使每一个包含它的模块都有一份它的复制,那也不会导致不一致。如果变量默认是内部链接,它是可变的量,所以在每个包含它的模块中,它的值可能会被改变,从而导致不一致的状况出现。


***?为什么类的静态数据成员不可以就地初始化?

答:

因为类体一般是放在头文件中的,如果允许其静态成员就地初始化,那就相当于允许在头文件中定义变量了。


链接过程

链接器一般会一次做出如下动作

(1)决定每一个obj(模块)在可执行文件中的位置。

(2)查看每一个模块的重定向表,给需要重定向的值加上它所在模块在可执行文件中的起始位置,形成正确的地址。

(3)检查所有模块的导出符号表,如果发现导出符号有重复,会产生链接错误: duplicated external simbols...,然后停止链接过程。

(4)在导出符号表中搜索未解决符号表中的符号,找到后会在相应位置填上正确地址,如果找不到,就会产生链接错误: unresolved external link...,然后停止链接过程。

 (5)完成链接过程,生成可执行文件。


原创粉丝点击