目录

  • 1. gcc的工作原理
    • 1.1 gcc介绍
    • 1.2 举例说明
  • 2 可执行二进制文件的生成过程
    • 2.1 预处理
    • 2.2 编译
    • 2.3 汇编
    • 2.4 链接
    • 2.5 检查是否链接
  • 3 动态库(共享库)
  • 4 静态库
  • 5 静态链接与动态链接
    • 5.1 动态链接
    • 5.2 静态链接
    • 5.3 查看可执行程序的链接方式

1. gcc的工作原理

1.1 gcc介绍

gcc 是一个编译器,是可以将文本类文件翻译成机器可以执行的二进制可执行文件。
程序的翻译过程有以下几步:

  1. 预处理
    包括头文件展开、条件编译、宏替换和去注释等操作。
  2. 编译
    将c语言编译形成汇编语言。
  3. 汇编
    将自己的代码通过汇编语言翻译成可重定位二进制文件,最终形成的二进制文件是不可以执行的。自己写的代码指的是自己声明定义的函数,而不包括入 printfscanf 等标准库函数。
  4. 链接
    将自己形成的 .obj 文件和库文件进行合并形成可执行程序。

1.2 举例说明

下面通过一个例子来编译一个.c文件:

1、首先创建一个 myfile.c 文件,用 vim 打开并编写一段代码,保存退出。图示如下:


2、使用命令 gcc myfile.c -o myfilegcc -o myfile myfile.c 命令会编译 myfile.c 文件形成可执行文件myfile,记住 -o 后面紧接着的就是要形成可执行文件的名称。图示如下:


3、使用 ./myfile 运行可执行程序 myfile,可以看到输出结果。图示如下:

2 可执行二进制文件的生成过程

2.1 预处理

根据上面的介绍,以如下程序为例进行预处理过程的介绍

示例代码如下:

#include 2 #define PRINT3 #define M 10 4 int main()5 {6 printf("Hello World! start\n");7// printf("Hello World!\n");8// printf("Hello World!\n");9// printf("Hello World!\n"); 10// printf("Hello World!\n"); 11// printf("Hello World!\n"); 12// printf("Hello World!\n"); 13// printf("Hello World!\n"); 14// printf("Hello World!\n"); 15// printf("Hello World!\n"); 16 printf("Hello World! end\n"); 17 #ifdef PRINT 18 printf("Hello PRINT\n"); 19 #else 20 printf("Hello None\n"); 21 #endif 2223 return 0; 24 }

使用命令: gcc -E myfile.c -o myfile.i
解释:-E表示从现在开始进行程序的翻译,预处理做完就停下。-o** 表示指定预处理后的文件写入到另一个 .i 文件里。打开 myfile.c(左)和 myfile.i(右) 文件如下:

1、 头文件展开:
可以看到 myfile.i 文件有八百多行,由于在写代码时引入了头文件 stdio.h,而 stdio.h 头文件里也引入了头文件,多出来的内容就是把 stdio.h 头文件里的内容拷贝到 myfile.c 文件中。这个操作就是头文件展开。如果要避免重复拷贝,可以使用 progma once 命令。

2、 去注释
可以看到 myfile.c 文件中的注释被编译器裁剪掉了。

3、 宏替换
M 替换成了100

4、 条件编译
由于宏 PRINT 被定义了,所以保留了 printf(“Hello PRINT\n”);这段代码。

2.2 编译

使用命令: gcc -S myfile.i -o myfile.s
解释: -S 表示从现在开始做程序的翻译,当编译做完就停下来。-o 表示告诉编译器要形成一个 myfile.s 文件存储编译好的内容。打开 myfile.s ( 形成的汇编语言) 内容如下:

2.3 汇编

使用命令: gcc -c myfile.c -o myfile.o
解释: 将自己的代码翻译形成二进制目标文件(.obj文件)。-c 表示从现在开始进行程序的翻译,汇编做完就停下。打开 myfile.o 文件可以看到一堆乱码,表明此文件并不是文本文件。注意 .o 文件不能执行。

2.4 链接

使用命令: gcc myfile.o -o myfile
解释: 表示将 .o 的可重定向的二进制文件链接成可执行二进制文件 myfile。如下图所示:


综上就完成了程序的翻译过程。

2.5 检查是否链接

ldd myfile 查看自己被形成的时候依赖了哪些库,可以看到依赖了如下几个库。

几个问题:

1、为什么能够在 linux 下进行 C/C++ 的编译呢?
linux 系统默认已经携带了语言级别的头文件和语言对应的库。在写C语言的时候都要先包含 stdio.h 头文件,其意义就是方便引入 printf 以及 scanf 等接口来进行程序交互。

2、为什么 int double 等可以被识别?
 因为编译器对其进行了解释。

3、为什么可以调用 printf 等这样的接口?
 因为c语言提供了基本库。

总结:编译器 + 就可以帮我们把写好的代码编译成二进制可执行程序。所以写代码时需要有头文件库文件,一般在安装 ide(集成开发环境) 的时候,需要安装语言的头文件和库文件才能编译代码,头文件会告诉我们库文件里有哪些函数,库文件里有函数的实现方法。在 linux 种,头文件在 /usr/include 目录下,内容如下:

3 动态库(共享库)

  1. 动态库在整个系统里只有一份,动态库被所有人共享,在链接器进行链接的时候,只需要把需要调用库的地址加载到程序中,这就完成了链接的过程。
  2. 在进行链接过程的时候,把动态库对应的程序(如调用了printf)的地址拷贝到可执行程序里,当程序加载运行,需要调用库函数的时候,程序就会跳转到库中去运行。这样就完成了动态库链接的过程。
  3. linux 动态库以 .so 结尾。自己写的程序程序加载到内存中,需要调用库函数的时候,就跳转到库中去运行。总之动态库链接的过程就是把需要调用的动态库里的函数地址拷贝到程序里。
  4. 动态库可以做到多个程序共享,真正实现永远在库中,程序内部只有地址,比较节省空间。

4 静态库

  1. Linux种静态库以前缀 lib 开头以及后缀 .a 结尾一般在读取库名称的时候,需要将前缀和后缀去掉。
  2. 静态链接是将需要的库中的代码拷贝到程序中,动态链接是将需要库的地址拷贝到程序中。
  3. 静态库由于自身拷贝问题,比较浪费空间。当多个程序需要调用相同的函数,会导致从静态链接库中复制多份函数代码到程序中,如果三个程序同时运行导致内存中含有多份相同的代码,导致占用空间变大。如果程序编译好了放在磁盘上浪费磁盘空间,或者程序加载到内存里浪费内存空间。

5 静态链接与动态链接

库分为静态库和动态库,静态库是编译器专门让用户程序进行静态链接的,动态库是让编译器对用户的程序进行动态链接的。

5.1 动态链接

动态链接是找到动态库,拷贝动态库中所需要代码的地址到可执行程序中相关的位置。
动态连接成功: 程序依赖动态库,一旦动态库缺失程序就无法运行。
输入指令 file myfile 查看可执行程序是静态链接还是动态链接,如下所示该指令显示为动态链接。

5.2 静态链接

静态链接是找到静态库,拷贝静态库中所需要的代码到自己的可执行程序中。
静态链接成功: 程序不依赖任何库,自己就可以独立运行。

linux 系统默认采用动态链接方式,使用 gcc myfile.c -o myfile_static -static 命令采用静态链接方式形成可执行文件,由图中可以看到静态链接和动态链接程序体积相差很大,图示如下:

此时再执行 file myfile_static 可以看到该可执行程序采用的方式是动态链接,图示如下:

5.3 查看可执行程序的链接方式

输入指令 file myfile 查看可执行程序是静态链接还是动态链接,如下所示该指令显示为动态链接。