gcc hello.c时候发生了什么
gcc时经过了四个步骤:预处理,编译,汇编,链接
预处理
gcc -E
处理源代码hello.c和其相关的头文件(stdio.h):将其生成为一个hello.i文件
预处理干了什么?
预处理主要处理那些以#开头的预编译指令:如#include,#define
1.将所有的#define删除,并且展开原来的宏定义
2.处理所有条件编译指令:#if”、”#ifdef”、“#elif”、“#else”、”#endif”
3.处理#include,将被包含的文件插入到该项预编译指令的位置(这个过程是递归执行的:被包含的文件也可能有#include这个指令)
4.删除所有注释
5.添加行号和文件名标识符,编译时候出错可以找到位置
6.保留所有#pragma指令,编译时我们要使用他
编译
gcc -S
编译:编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件
编译一般可以分为六个步骤:词法分析,语法分析,语义分析,源代码优化,代码生成,目标代码优化
其实就是源代码分析和优化和目的代码的生成和优化
词法分析:源代码的程序被输入到扫描器,把源代码的字符序列分成一系列的记号,产生的记号一般分为关键字,标识符,字面量(数字,字符串)和特殊符号,当然在完成这些之后,扫描器也完成了其他的工作,将字面量常量存放到文字表,标识符存放在符号表。
语法分析:语法分析器对扫描后的产生的记号进行语法分析,从而生成语法树,简单来讲由语法分析器生成的语法树就是以表达式为节点的树,语法分析阶段必须对表达式内容进行区分,如果出现了表达式不合法,比如各种括号不匹配、表达式中缺少操作符等,编译器就会报告语法分析阶段的错误。
语义分析:语法分析只是完成了对表达式语法层面的分析,但它并不了解这个语句是否真的有意义,比如c语言中两个指针做乘法运算是没有意义的,但是这个语句在语法上是合法的。语法分析能分析出来的就只有静态语义错误(能在编译期间可以确认的错误),比如当一个浮点数的表达式赋给一个整形数隐含了一个浮点到整形类型转换的过程,语法分析过程需要完成这个步骤,再之,如果把一个浮点数赋给一个指针变量,语义分析会发现这个类型不匹配,编译器会报错。
与之相对的是动态语义,是运行时候才可以确认的。典型的错误如:变量取值为零时作为除数,数组元素引用时下标出界。
之后就是源代码优化和目标代码的生成和优化。
汇编
gcc -c
汇编:将汇编代码转换为机器可以执行的机器代码,就只是根据汇编和机器指令的对照表翻译就可以了
(静态)链接
人们把每个源代码独立的编译,然后按照需求把他们组装起来,这个组装模块的过程就是链接。
链接过程包括了空间和地址分配,符号决议(也可以称为名称决议),和重定位。
静态链接的过程:每个模块(.c)经过编译器生成目标文件(.o),这些目标文件和库一起链接最终形成了可执行文件
链接的例子:比如我们再main()函数中调用了func.c中的foo(),我们在每一处使用foo函数时候都得知道foo函数的地址,但由于每个模块都是单独编译的,在编译器编译main函数时,他并不知道foo函数的地址,所以他暂时把调用foo函数的地址暂时设置为0,等待最后链接的时候由编译器去把这些指令的目标地址修正。暂时设置为0的地方又叫做重定位入口,重定位就是给这样的重定位入口修改为正确的地址数值。
使用连接器可以直接引用其他模块的函数和全局变量而不需要知道他们的地址