前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux系统编程】——深入理解 GCC/G++ 编译过程及常用选项详解

【Linux系统编程】——深入理解 GCC/G++ 编译过程及常用选项详解

作者头像
用户11286421
发布2025-01-17 09:10:51
发布2025-01-17 09:10:51
29200
代码可运行
举报
文章被收录于专栏:学习学习
运行总次数:0
代码可运行
前言: 在C/C++开发中,编译器扮演着至关重要的角色,帮助我们将源代码转化为计算机可以直接运行的可执行程序。本文将带你深入了解 GCC/G++ 的编译过程、常用编译选项及相关知识,助你提升开发效率和问题定位能力。

⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装 Centos yum install glibc-static libstdc++ -static -y

1. GCC/G++ 编译过程

GCC/G++ 的完整编译过程可以分为以下 4 个阶段:

预处理(Preprocessing)

预处理是编译的第一个阶段,主要功能包括:

  1. 宏替换: 替换代码中定义的宏。
  2. 条件编译: 根据宏定义有选择地编译部分代码。
  3. 头文件展开: 将包含的头文件内容插入到代码中。
  4. 注释去除: 删除代码中的注释。
代码语言:javascript
代码运行次数:0
复制
gcc -E hello.c -o hello.i
  • -E:只执行预处理,不进入编译阶段。
  • -o:指定输出文件名,.i 文件为经过预处理的代码文件。

编译(Compilation)

编译阶段会:

检查代码的合法性(如语法错误)。 将代码翻译成汇编语言。

代码语言:javascript
代码运行次数:0
复制
gcc -S hello.i -o hello.s
  • -S:只进行编译,不执行汇编操作,生成 .s 汇编文件。

汇编(Assembly)

汇编阶段将 .s 汇编文件转化为二进制目标代码,生成 .o 文件(目标文件)。

代码语言:javascript
代码运行次数:0
复制
gcc -c hello.s -o hello.o
  • -c:只进行汇编,不执行链接,生成 .o 目标文件。

连接(Linking)

连接阶段将多个 .o 目标文件和库文件链接成一个可执行文件。

代码语言:javascript
代码运行次数:0
复制
gcc hello.o -o hello
  • 默认链接动态库(如 libc.so.6),生成的可执行文件可以直接运行。

静态链接与动态链接

在实际开发中,通常需要多个源文件协作完成一个程序,而这些源文件之间往往存在函数调用的依赖关系。为了解决这种依赖问题,编译器提供了 静态链接 和 动态链接 两种方式。

静态链接

定义: 在编译链接阶段,将库文件的代码直接嵌入到可执行文件中。 在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。静态链接的缺点很明显:

浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本; 更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快。

优点:

  • 运行时无需依赖外部库,执行速度快。

缺点:

  • 文件体积较大,多个程序共享库文件时会浪费存储空间。 更新库代码后,需要重新编译所有相关程序。 静态库文件后缀: .a(Linux)或 .lib(Windows)。

动态链接

定义: 在运行时将库文件链接到程序中,节省存储空间和系统资源。 优点:

  • 程序体积小,节省内存。 更新库文件后,无需重新编译相关程序。

缺点:

  • 运行时需要依赖动态库,若缺失动态库则程序无法运行。

动态库文件后缀: .so(Linux)或 .dll(Windows)。

查看动态链接的库:

代码语言:javascript
代码运行次数:0
复制
ldd hello
输出:
linux-vdso.so.1 =>  (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)

静态与动态链接示例 生成静态链接文件:

代码语言:javascript
代码运行次数:0
复制
gcc -static hello.o -o hello

可执行文件中包含所有依赖的库代码,运行时无需依赖外部动态库。 生成动态链接文件:

代码语言:javascript
代码运行次数:0
复制
gcc hello.o -o hello

默认情况下,GCC 使用动态链接,文件体积较小,运行时依赖动态库。

在这⾥涉及到⼀个重要的概念: 库 • 我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实“printf”函数的呢? • 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库⽂件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进⾏查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,⽽这也就是链接的作⽤

静态库和动态库

静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。其后缀名⼀般为“.a” • 动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。动态库⼀般后缀名为“.so”,如前⾯所述的libc.so.6 就是动态库。gcc 在编译时默认使⽤动态库。完成了链接之后,gcc 就可以⽣成可执⾏⽂件,如下所⽰。 gcc hello.o –o hello • gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过 file 命令验证。

  • Linux下,动态库XXX.so, 静态库XXX.a
  • Windows下,动态库XXX.dll, 静态库XXX.lib

GCC 常用选项

GCC 提供了丰富的编译选项,帮助开发者灵活地控制编译过程。以下是一些常用选项的介绍:

  1. 基本编译选项 -E:仅执行预处理。 -S:执行编译,生成汇编代码,不进行汇编。 -c:执行汇编,生成目标代码,不进行链接。 -o :指定输出文件名称。
  2. 链接相关选项 -static:生成静态链接的可执行文件。 -shared:生成动态库文件(.so)。 默认情况下,GCC 使用动态链接。
  3. 优化相关选项 -O0:不进行优化(默认)。 -O1:进行基本优化。 -O2:进行更高级别的优化,权衡运行效率和编译时间。 -O3:最高优化级别,开启所有优化选项。
  4. 调试相关选项 -g:生成调试信息,供调试器(如 gdb)使用。 -w:不生成任何警告信息。 -Wall:生成所有警告信息,建议开发时开启。

关于编译器的周边

条件编译的应用场景

什么是条件编译? 条件编译是通过宏定义或者预处理指令,控制代码的某些部分在编译时是否被包括进编译过程的功能。它通过预处理指令(如 #ifdef、#ifndef、#if)实现。

应用场景: 多平台兼容 条件编译可以根据操作系统或者硬件平台生成不同的代码。

代码语言:javascript
代码运行次数:0
复制
#ifdef _WIN32
printf("Running on Windows\n");
#else
printf("Running on Linux\n");
#endif

这段代码会根据操作系统定义,选择性地编译不同的部分,从而实现跨平台兼容。

调试与发布 在开发和调试阶段,通常需要输出大量的日志信息,但在发布版本中不希望这些日志被编译进去。条件编译可以帮助实现这一需求。

代码语言:javascript
代码运行次数:0
复制
#define DEBUG
#ifdef DEBUG
printf("Debugging...\n");
#endif

如果 DEBUG 宏被定义,则会输出调试信息;否则,这段代码不会被编译。 还有节约资源、功能模块化、版本控制等场景!

为什么非得把语言变成汇编

编译器的职责 编译器的职责是将高级语言(如 C/C++)编写的程序,转换为计算机可以理解并执行的低级语言(机器代码)。在这个过程中,汇编语言作为一个中间步骤,是不可或缺的。

原因详解 硬件直接执行机器代码 计算机硬件只能理解机器码(由 0 和 1 组成的二进制指令)。因此,无论程序用哪种高级语言编写,最终都必须被转换为机器码。

汇编语言是机器码的可读形式 汇编语言是一种人类可读的机器码表示形式,它将二进制指令映射为助记符(如 MOV、ADD)。将高级语言转化为汇编语言,可以更容易地检查、优化和调试程序。

编译器优化的便利性 编译器将高级语言代码转化为汇编语言,可以应用一系列优化技术(如寄存器分配、指令重排序等),以生成高效的机器码。

多平台适配性 汇编语言是与具体硬件架构相关的。如果编译器直接生成机器码,可能难以适配不同的平台。生成汇编语言后,可以通过调用汇编器生成适合目标平台的机器码。

调试和错误检查 转换为汇编语言后,开发者可以通过反汇编工具查看生成的汇编代码,从而更容易找到性能瓶颈或逻辑错误。

历史原因 早期的计算机直接通过汇编语言编程,高级语言的编译器是后来发展的。为了保持兼容性和硬件操作的透明性,汇编语言仍然是现代编译器中一个重要的中间步骤。

总结 将语言转化为汇编的步骤是编译器中一个关键的阶段,它在硬件和高级语言之间建立了一座桥梁,使程序既能保持可读性,又能高效运行。

编译器自举(Compiler Bootstrap)

什么是编译器自举? 编译器自举是指使用一个已有的简单版本编译器,来开发并编译更加复杂或功能完整的编译器。这种过程通常用于构建编译器自身。

为什么需要自举? 解决编译器的“鸡与蛋”问题 编译器需要用某种语言实现。如果用目标语言本身实现编译器,如何运行该编译器?自举解决了这一问题。

验证编译器的正确性 如果一个编译器能够成功地编译自身并运行生成的版本,说明这个编译器的实现是可靠的。

便于跨平台移植 一个简单的自举编译器可以快速移植到新平台上,然后用它生成完整版本的编译器。

以 C 编译器为例: 第一阶段: 用汇编语言实现一个简单的 C 编译器(只支持部分语法)。 第二阶段: 用第一阶段的编译器编写一个功能更完善的 C 编译器。 第三阶段: 用第二阶段的编译器编译自身,生成最终的完整编译器。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言: 在C/C++开发中,编译器扮演着至关重要的角色,帮助我们将源代码转化为计算机可以直接运行的可执行程序。本文将带你深入了解 GCC/G++ 的编译过程、常用编译选项及相关知识,助你提升开发效率和问题定位能力。
  • 1. GCC/G++ 编译过程
    • 预处理(Preprocessing)
    • 编译(Compilation)
    • 汇编(Assembly)
    • 连接(Linking)
  • 静态链接与动态链接
    • 静态链接
    • 动态链接
    • 静态库和动态库
  • GCC 常用选项
  • 关于编译器的周边
    • 条件编译的应用场景
    • 为什么非得把语言变成汇编
    • 编译器自举(Compiler Bootstrap)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档