编译器包括:预处理器、编译器、汇编器、链接器。
file.c + file.h --> 预处理器 ————>file.i --> 编译器 --> file.s --> 汇编器 --> file.o
预编译:
处理所有的注释,以空格代替;
将所有的#define删除,并且展开所有的宏定义;
处理条件编译指令:#if、#ifdef、#elif、#else、#endif;
处理#include,展开被包含的文件;
保留编译器需要使用的#pragma。
预处理指令示例:
gcc -E file.c -o file.i
编译:
对预处理文件进行词法分析、语法分析和语义分析。
词法分析: 分析关键字,标识符,立即数等是否合法;
词法分析:分析表达式是否遵循语法分析;
语义分析:在语法分析的基础上进一步分析表达式是否合法。
分析结束后进行代码优化生成相应的汇编代码文件。
编译指令示例:
gcc -S file.c -o file.s
汇编:
汇编器将汇编代码转变为机器的可执行指令;
每条汇编语句几乎都对应一条机器指令。
汇编指令示例:
gcc -c file.s -o file.o
链接器:
gcc file.o //生成a.out
链接过程:
工程中的每个C语言源文件被编译后生产目标文件,这些目标文件如何生成最终的可执行程序?
链接器:链接器的主要作用就是把各个模块之间相互作用的部分处理好,使得各个模块之间能够正确的衔接。
静态链接:由链接器在链接时将库的内容直接加入到可执行程序中:
file1.0 + file2.0 + libc.a --> 链接器 --> a.out
Linux下静态库的创建和使用:
编译静态库源码:gcc -c lib.c -o lib.o 生成静态库文件:ar -1 lib.a lib.o 使用静态库编译:gcc main.c lib.a -o main.out
动态链接:
可执行程序在运行时才动态加载库进行链接;
库的内容不会进入可执行程序中。
lib1.so --> stub1 --> 链接器linker --> a.out lib2.so --> stub2 -->
Linux下动态库的创建和使用:
编译动态库源码: gcc -shared dlib.c -o dlib.so 使用动态库编译: gcc main.c -ldl -o main.out //-ldl 编译选项:使用动态链接的方式
观察下面代码,体会动态库的使用:
关键系统调用:
dlopen: 打开动态库文件 dlsym: 查找动态库中的函数并返回调用地址 dlclose: 关闭动态库文件
代码如下:
#include <stdio.h> #include <dlfcn.h> int main() { void* pdlib = dlopen("./dlib.so", RTLD_LAZY); char* (*pname)(); int (*padd)(int, int); if( pdlib != NULL ) { pname = dlsym(pdlib, "name"); padd = dlsym(pdlib, "add"); if( (pname != NULL) && (padd != NULL) ) { printf("Name: %s\n", pname()); printf("Result: %d\n", padd(2, 3)); } dlclose(pdlib); } else { printf("Cannot open lib ...\n"); } return 0; }
删除.so文件后,.out文件将无法执行。
~/will$ gcc -shared dlib.c -o dlib.so ~/will$ gcc test.c -ldl -o main.out ~/will$ ./main.out Name: Dynamic Lib Result: 5
小结:链接是指将目标文件最终链接为可执行程序;
根据链接方式的不同,链接过程可分为:
静态链接:目标文件直接链接进入可执行程序:(将库整合进一应用程序,适合一部分小程序)
动态链接:在程序启动后才动态加载目标文件。