一个C程序最后生成可执行目标文件,会分阶段经过预处理, 编译,汇编 和 链接的过程,而往往整个过程由IDE提供的编译驱动程序, 全权代表用户调用预处理器,编译器,汇编器和链接器。

预处理
#include <stdio.h>
int main()
{
printf("hello world\n");
}
可以使用gcc -E hello.c -o hello.i
或者cpp hello.c > hello.i
命令将 hello.c 文件被预编译成一个hello.i 文件。
一般说,预处理器将会做以下事情:
- 去掉注释
- 展开所有宏定义
- 处理
#include
预编译指令,复制头文件,这个过程是递归进行的。 - 处理所有的条件预编译指令:
#if
#ifdef
#elif
#else
#endif
- 保留所有
#pragma
编译器指令
编译
可以使用gcc -S hello.i -o hello.s
将预处理后的文件变为汇编文件。
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。编译器过程非常复杂,这里不作详细阐述。
-
词法分析
array[index] = (index + 4) * (2 + 6)
源代码程序被输入到扫描器,扫描器的任务很简单,它只是简单的进行词法分析,运用一种有限状态机的算法将源代码的字符序列分割成一系列的记号。
词法分析的记号一般分为:关键字,标识符,字面量和特殊符号。
-
语法分析
语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树。由语法分析器产生的语法树是以表达式为节点的树。
- 语义分析
语法分析仅仅是完成了对表达式的语法层面的分析,但是它并不能了解这个语句是否真正的意义。比如:C语言中两个指针做减法是没有意义的,但是这个语句在语法上是合法的。
编译器所能分析的是静态语义,所谓静态语义是指在编译器可以确定的语义,与之对应的是动态语义,指只有在运行期才能确定的语义。
汇编
可以使用gcc -c hello.s -o hello.o
或者as hello.s -o hello.o
,将汇编语言文件生成目标文件。as是汇编器,它的作用是直接将汇编代码转变成机器指令,每一个汇编语句几乎都对应一条机器指令。
链接
-
为什么需要链接?
在一个程序被分割成多个模块之后,这些模块之间如何组合形成一个单一的程序是必须解决的问题,模块之间如何组合的问题可以归结为模块之间如何通信的问题。最常见的属于静态语言的C/C++模块之间通信的两种方式:- 模块间的函数调用
- 另外一种是模块间的变量访问。
函数访问必须知道目标函数的地址,变量访问必须知道目标变量的地址。所以两种方式都可以归结为一种方式:那就是模块间符号的引用。完成模块间符号引用的过程就是链接。链接的主要过程包括地址和空间分配,符号决议 和 重定位。
-
对于链接时出现的"usr/bin/ld: cannot find -lxxx 的错误"
比如这种,"usr/bin/ld: cannot find -labc",首先要知道,这里labc
表示的是libabc.so
这个动态库。出现这种情况:
- 第一种是你确实没有安装这个动态库,这个需要你自己安装。
- 第二种情况是:动态库放置位置不对。
系统识别动态库路径配置在etc/ld.so.conf
文件中。
所以,当gcc编译链接时没有显示指定目录,那么链接器将会默认搜索:cat /etc/ld.so.conf include /etc/ld.so.conf.d/*.conf cat /etc/ld.so.conf.d/*.conf # libc default configuration /usr/local/lib /usr/lib # Multiarch support /lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu
/usr/local/lib
,/usr/lib
,/lib/x86_64-linux-gnu
,/usr/lib/x86_64-linux-gnu
。
而你的位置放置的不对,这时可以手动拷贝至上面的目录中,
或者建立符号链接:ln -s 这个库的路径/libabc.so /usr/lib/libabc.so
手动拷贝至上面目录或者建立符号链接之后,需要用
ldconfig
刷新缓存,可以用ldconfig -p
查看动态库列表,是否已经更新。
附:gcc 编译选项
文档:https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/
功能 | 命令 |
---|---|
预处理 | gcc -E hello.c -o hello.i |
编译(生成汇编) | gcc -S hello.i -o hello.s |
汇编(机器指令) | gcc -c hello.s -o hello.o |
指定源码目录 | -I(大写的i),多个源码目录,每一个都需要在前面指定-I |
链接动态库 | -l (小写的L)放在/usr/lib等目录里的库直接用参数就能链接了 |
-L参数跟着的是库文件所在的目录名(比如/home/libabc.so,需要用:-L/home -labc (一般的在-L的后面可以一次用-l指定多个库文件) |
参开资料
1.《程序员的自我修养》
网友评论