目录
一、从源代码到可执行程序的主要过程
预处理 -> 编译 -> 汇编 -> 链接
二、linux环境下过程介绍
1.预处理
将后缀为.c的文件,处理成一个没有宏定义、没有条件编译指令、没有特殊符号、没有注释,后缀为 .i的文件。(.cpp/.cxx 则生成 .ii)
a.头文件展开:将引用的头文件的内容复制到当前文件中。(这个过程是递归进行的,因为头文件中可能包含其他头文件)
b.宏替换:展开所有的宏定义,即将文件中的所有宏名替换为相应的字符串。(C语言的宏替换和文件包含的工作,不归入编译器的范围,而是交给独立的预处理器)
c.条件编译:#ifdef、#ifndef、#else、#elif、#endif等等。
d.去掉注释。
e.保留所有的#pragma 编译器指令:编译器指令有特殊作用,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。
f.添加行号和文件标识:便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
2.编译
对xxx.i或xxx.ii文件,进行语法检查后生成相应的汇编代码文件(.s文件)。
a.语法检查:词法分析、语义分析、符号汇总和语法分析及优化。
b.生成汇编代码:在确认所有的指令都符合语法规则之后,将其翻译成。
3.汇编
根据编译后生成的 .s文件,把汇编代码转换为二进制机器码,生成目标文件(.o(Windows下)、.obj(Linux下))
4.链接
a.对于函数声明与定义都在不同文件里的文件,链接的作用就是将这些.o文件合并。
(如:函数声明在test.h中,定义在test.cpp中,use.cpp使用了其中的函数,链接时要将它们对应的.o文件合并。)b.除此之外,链接时还要确定变量、数组和函数的地址。
经过以上4个步骤,我们就得到了 可执行文件。(.exe文件、.out文件)
以下为linux环境下从源代码到可执行程序的过程简介图
三、拓展
1.链接时如何靠.h文件中的声明找到对应.c文件下的定义?
借助符号表,存储变量和函数的地址映射。
在.c文件汇编时在.o文件中会生成一个符号表,链接时根据变量名、函数名在对应.c文件的符号表中找到其地址,所以我们可以靠.h文件中的声明找到对应.c文件下的定义。
2.在.h文件中定义全局变量
所以我们要谨慎地在.h文件中定义全局变量,因为.h文件会在所有引用了它的地方展开,如果.h文件中有全局变量,又恰好又多个.c文件引用了.h文件,这样整个工程中会出现多个同名的全局变量,在符号表合并时该变量会有多个不同的地址,会导致链接错误。
在.h文件中正确定义全局变量的方法是加一个extern使其变为声明,然后在存放定义的.c文件中对其进行定义,因为定义时变量和函数的地址才会进入符号表,这样在符号表合并时全局变量只有一个地址,不会导致链接错误。
或是在.h文件将其定义为静态变量(static),修改其链接属性,使其变为当前文件可见,使其不再放入符号表中。即便展开后在引用了它的.c文件中会各自有一个静态变量,且这些静态变量的地址都不同,但其不会放入符号表中,其它文件不可见,故不会有链接错误。
eg.在头文件中定义全局变量
3.编译错误和链接错误
a.编译错误:语法错误。
b.链接错误:无法在其它文件中找到引用的函数、函数的重定义……