深入理解GCC 编译

虽然平时经常用gcc来开发,但从没有细想过其内部怎么执行,其具体的流程又是如何。由于主力开发语言为C/C++,加上最近看C专家编程有感,决定写此博客,来加深自己的记忆。

1 编译流程

通常GCC的编译流程为:

  • 预编译-预处理源文件,生成.i文件:去掉注释、宏替换、增加行号信息等步骤;
  • 编译-编译预处理后的.i文件,生成.s汇编代码:代码语法分析、代码优化和汇总符号等步骤
  • 汇编-将汇编代码编译为目标文件;
  • 连接-连接目标文件和附加的目标文件,最终生成可执行文件。

其流程如下图所示:

在这里插入图片描述

示例代码main.c如下所示:

#include <stdio.h>

int main()
{
    int a = 1, b = 2;
    
    printf("The result of a + b is: %d\n", a + b);
    
    return 0;
}

通常我们的做法是:

gcc main.c -o main
gcc -Wall main.c -o main	# add warning info.

1.1 预编译

gcc -E main.c -o main.i

只激活预处理,生成的.i文件,即在源.c文件前面会插入头文件的内容,由于文件较大,可以只查看最后20行,可以通过下面指令查看,其内容自行查看即能理解。

# view all
vim main.i		# by vim
code main.i		# by vscode

# view the last 20 row
tail -n 20 main.i

1.2 编译生成汇编代码

gcc -S main.c -o main.s
gcc -S main.i -o main.s

只激活预处理和编译,将文件编译成为汇编代码。可以对源.c文件或者预处理后的文件进行编译操作。

1.3 汇编

gcc -c main.s -o main.o

对于前面编译生成的汇编代码,将其进行编译生成目标文件。

1.4 连接

gcc main.o -o main

执行./main,即会弹出:

$ ./main
The result of a + b is: 3

gcc的连接器通常为gas,其将程序目标文件和其他附加的目标文件连接起来,最后生成可执行文件。

1.5 连接多个文件

如果我们编写的文件,包含多个.c和头文件时,怎么编译呢?这里以三个文件为例,参考如下的代码:

gcc test1.c test2.c test3.c main.c -o test

其等价于以下内容:

gcc main.c -o main.o
gcc -c test1.c -o test1.o
gcc -c test2.c -o test2.o
gcc -c test3.c -o test3.o
gcc test1.o test2.o test3.o main.o -o test 

注意:头文件需要包含三个.c文件的相关申明,对于依赖最好写Makefile文件

1.6 错误检查

通常我们会使用-Wall 打开警告信息,但还有其他的一些命令,如下:

gcc -Wall main.c -o main
gcc -pedantic main.c -o main
gcc -Werror main.c -o main
gcc -Wextra main.c -o main

将测试代码中的打印输出,改为打印c,来看一下错误检查信息。-Wall选项,不仅给出了错误信息,也给出了详细的警告信息。

$ gcc -Werror main.c -o main
main.c: In function 'main':
main.c:7:44: error: 'c' undeclared (first use in this function)
     printf("The result of a + b is: %d\n", c);
                                            ^
main.c:7:44: note: each undeclared identifier is reported only once for each function it appears in

$ gcc -Wall main.c -o main
main.c: In function 'main':
main.c:7:44: error: 'c' undeclared (first use in this function)
     printf("The result of a + b is: %d\n", c);
                                            ^
main.c:7:44: note: each undeclared identifier is reported only once for each function it appears in
main.c:5:16: warning: unused variable 'b' [-Wunused-variable]
     int a = 1, b = 2;
                ^
main.c:5:9: warning: unused variable 'a' [-Wunused-variable]
     int a = 1, b = 2;
         ^

$ gcc -Wextra main.c -o main
main.c: In function 'main':
main.c:7:44: error: 'c' undeclared (first use in this function)
     printf("The result of a + b is: %d\n", c);
                                            ^
main.c:7:44: note: each undeclared identifier is reported only once for each function it appears in

$ gcc -pedantic main.c -o main
main.c: In function 'main':
main.c:7:44: error: 'c' undeclared (first use in this function)
     printf("The result of a + b is: %d\n", c);
                                            ^
main.c:7:44: note: each undeclared identifier is reported only once for each function it appears in

-Wall选项,返回GCC所有的警告信息;

-pedantic选项,能够帮助程序员发现一些不符合 ANSI/ISO C标准的代码;

-Werror选项,GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改;

-Wextra选项,对所有合法但值得怀疑的表达式发出警告。

1.7 语言标准

gcc -std=c11 main.c -o main
gcc -std=c99 main.c -o main

顾名思义,选择c11还是c99标准。

1.8 优化选项

gcc -O0 main,c -o mian
gcc -O1 main.c -o main
gcc -O2 main.c -o main
gcc -O3 main.c -o main
gcc -Os main.c -o main

-O0:不优化,缺省值;

-O1:尝试优化编译时间和可执行文件大小;

-O2:尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法。;

-O3:再打开一些优化选项:-finline-functions-funswitch-loops-fgcse-after-reload

-Os:对生成文件大小进行优化。打开-O2开的全部选项,除了会那些增加文件大小的。

2 外部库的连接

通常在开发时,不可能所有函数都是自己编写,不可避免的会使用第三方提供的库函数等。这些库函数通常由一组互相关联的可重用原则编写,而我们熟知的库函数如:.dll, .lib, .so等。库具有模块化、重用、可维护的特性。

库又可以分为静态库与动态库:

  • 静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。静态库比较占用磁盘空间,而且程序不可以共享静态库。运行时也是比较占内存的,因为每个程序都包含了一份静态库。
  • 动态库(.so):程序在运行的时候才去链接共享库的代码,多个程序共享使用库的代码,这样就减少了程序的体积。

C专家编程中关于动态链接和静态链接有这样的描述:

  • 如果函数库的一份拷贝是可执行文件的物理组成部分,那么我们称之为静态链接;
  • 如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库,那么称之为动态链接。

静态链接的模块被链接编辑并载入以便运行。动态链接的模块被链接编辑后再载入,在运行时进行连接以便运行。

Linux下,一般头文件或库文件的位置在:

  • /usr/include及其子目录底下的include文件夹
  • /usr/local/include及其子目录底下的include文件夹
  • /usr/lib
  • /usr/local/lib
  • /lib

静态库和动态库的生成和使用,参考如下两篇博客:
linux下 GCC编译链接静态库&动态库
Linux GCC编译使用动态、静态链接库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值