GCC 指令详解及动态库、静态库的使用( 二 )

1.3.2.2 编译# 通过添加参数 -S 将 test.i 转换为汇编文件 test.s(默认生成 .s 文件)$ gcc -S test.i$ gcc -S test.i -o test.s # 写法二1.3.2.3 汇编# 通过汇编得到二进制文件 test.o(默认生成 .o 文件,object)$ gcc -c test.s$ gcc -c test.s -o test.o # 写法二1.3.2.4 链接# 通过链接得到可执行文件 test$ gcc test.o -o test在成功生成 test.o 文件后,就进入了链接阶段 。在这里涉及到一个重要的概念:函数库 。
在 test.c 的代码中,我们通过print()函数打印 Hello World 语句;但是在这段程序中并没有定义 printf 的函数实现,且在预编译中包含进去的「stdio.h」中也只有该函数的声明extern int printf (const char *__restrict __format, ...);,而没有定义函数的实现,那么是在哪里实现的呢?
答案就是:系统把这些函数实现都做到了名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径 /usr/lib64 下进行查找,也就是链接到 libc.so.6 库函数中去,这样就有函数 printf 的实现了,而这也就是链接的作用 。

GCC 指令详解及动态库、静态库的使用

文章插图
而函数库一般分为静态库和动态库两种:
  • 静态库是指在编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不需要库文件了 。在 Linux 中静态库一般以 .a 作为后缀 。
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时链接文件加载库,这样就可以节省系统的开销 。在 Linux 中动态库一般以 .so 作为后缀 。
如前面所述的 libc.so.6 就是动态库,gcc 在编译时默认使用动态库 。完成了链接之后,gcc 就可以生成可执行文件了 。
有关动态库和静态库的详细介绍,将在下文进行具体讲解 。
1.3.2.5 总结最后,通过一张图来总结一下上述流程:
GCC 指令详解及动态库、静态库的使用

文章插图
在 Linux 下使用 GCC 编译器编译单个文件十分简单,直接使用$ gcc test.c(test.c 为要编译的 C 语言的源文件),GCC 会自动生成文件名为 a.out 的可执行文件(也可以通过参数 -o 指定生成的文件名);也就是通过一个简单的命令就可以将上边提到的 4 个步骤全部执行完毕了;但是如果想要单步执行也是没问题的 。
1.4 GCC 常用参数下面的表格中列出了一些常用的 gcc 参数,这些参数在 gcc 命令中没有位置要求,只需要编译程序的时候将需要的参数指定出来即可 。
gcc 编译选项解释说明-E预处理,主要是进行宏展开等步骤,生成 test.i-S编译指定的源文件,但是不进行汇编,生成 test.s-c编译、汇编源文件,但是不进行链接,生成 test.o-o指定链接的文件名及路径-g在编译的时候,生成调试信息,该程序可以被调试器调试-D在程序编译的时候,指定一个宏-std指定 C 方言,如 -std=c99 。gcc 默认的方言是 GNU C-l在程序编译的时候,指定使用的库(库的名字一定要掐头去尾,如 libtest.so 变为 test)-L在程序编译的时候,指定使用的库的路径-fpic生成与位置无关的代码-shared生成共享目标文件,通常用在建立动态库时1.4.1 指定一个宏(-D)在程序中我们可以通过使用#define定义一个宏,也可以通过宏控制某段代码是否能够被执行 。
#include <stdio.h>int main(){int num = 60;printf("num = %d\n", num);#ifdef DEBUGprintf("定义了 DEBUG 宏, num++\n");num++;#elseprintf("未定义 DEBUG 宏, num--\n");num--;#endifprintf("num = %d\n", num);return 0;}

经验总结扩展阅读