编译的四步骤
从源文件到可执行文件:预处理,编译,汇编和链接
- 预处理
gcc -E
hello.c --> hello.i
- 宏和头文件展开;删除注释,空白行;保留
#pragma
(它是编译器指令)
- 编译
gcc -S
hello.i --> hello.s
- 检查语法规范(词法分析,语法分析);生成汇编代码;消耗时间和系统资源最多的步骤
- 汇编
gcc -c
hello.s --> hello.o
- 将汇编指令翻译成机器指令;一对一翻译,相对机器来说比较简单
- 链接
no specific parameters
- 和其它的目标文件进行链接;数据段,符号表合并;符号重定位,地址回填;生成最终可执行文件
常用参数
-I
:指定头文件所在的目录-c
:只进行预处理,编译和汇编,生成二进制文件
-g
:编译时加入 debug 信息-On
:n 取 0 ~ 3,编译优化,值越大,优化的越多-l
:指定库的库名-L
:指定库的搜索路径-Wall
:显示所有警告信息-D
:向程序中动态注册宏定义,一般起到「开关」的作用静态库和动态库
静态库是在程序运行前就已经加入到程序代码中,成为程序代码的一部分,程序整体体积可能会比较大;动态库是在程序运行时加载到执行程序代码中,可以被多个程序共享使用,运行时效率稍微低一点。
动态库相对独立,便于维护和更新;静态库和程序绑在一起,方便移植,不会出现依赖问题。
静态库一般以.a
为后缀,动态库以.so
为后缀。
发布静态库时,要提供.a
和.h
文件;静态库制作过程
void hello() { printf(“hello world\n”); }
2. 编译生成`.o`文件
```bash
gcc -c hello.c -o hello.o
使用
ar
工具来制作静态库ar -rsc libhello.a hello.o
库名默认以
lib
作为前缀,以.a
作为后缀;.o
文件可以有很多个,将多个目标文件压成一个静态库。编译源文件,把静态库也加入编译 ```c
include “hello.h”
int main() { hello(); return 0; }
库要写在源文件后面。该库进行静态编译,其它库(例如`printf()`默认动态编译)。
```bash
gcc main.c libhello.a -o main
gcc main.c -o main -lhello -L. # 如果./只有.a文件可以这么编译,否则默认链接动态库
void hello() { printf(“hello world\n”); }
2. 编译生成`.o`文件,**此时需生成与位置无关的代码,使用**`**-fPIC**`**参数,表示各个变量、函数的地址使用相对地址,而不是绝对地址**
```bash
gcc -c hello.c -o add.o -fPIC
使用
gcc -shared
来制作动态链接库,.o
文件可以有很多个gcc -shared hello.o -o libhello.so
编译源文件,指定动态库的位置。
gcc main.c -o main -lhello -L.
可以看到图中多了一个动态库的信息;可执行文件体积也更小。配置动态链接器的库搜索路径;运行
动态链接库是运行时被加载,要保证在运行时动态链接器能找到这个库文件。上面截图显示libhello.so
未被找到。
- 将库文件移动至系统默认的搜索路径下(例如,
/usr/lib
) - 设置库搜索的环境变量
LD_LIBRARY_PATH
;当前命令行设置,一次性,shell 关闭后失效;在.xxshrc
配置文件中设置;建议使用绝对路径。
修改
/etc/ld.so.conf
配置文件,将库绝对路径加入到文件中;执行ldconfig
更新配置。GCC 内联函数
声明内联函数有两种方式:
inline
__attribute __((always_inline))
第一种是建议编译器使用内联,实际是否内联由编译器来决定;第二种是强制编译器将函数当作内联函数。