当前位置: 首页 > news >正文

Linux---Linux编译器-gcc/g++的使用

什么是gcc/g++?

在Linux中,gcc 和 g++ 都属于编译器,是将代码(C/C++)翻译成机器能执行的二进制文件的工具,但gcc 和 g++ 不只是“单一的编译工具”,它们本质是“编译工具链”的前端入口,能串联“预处理、编译、汇编、链接”4个核心步骤,从源代码直接生成最终的可执行文件(或库文件),相当于“一站式处理”代码到可执行文件的全流程。

  • g++ 是“专属 C++ 的”:专门用来编译 C++ 代码(.cpp 文件),能自动处理 C++ 的类、模板,还会默认加载 C++ 标准库,不用额外配置。
  • gcc 不只是“C 语言的”:它能编译 C 代码(.c 文件),也能编译 C++ 代码,但编译 C++ 时要手动加参数加载库,不如g++ 方便,日常更常用它处理 C 语言。
  • 两者的选项完全一样

一个C/C++程序从源代码到可执行程序的编译过程:

  1.  预处理:1、去注释 2、头文件的展开 3、宏替换 4、条件编译
  2.  编译:将C语言转换为汇编语言
  3.  汇编:汇编语言转换为目标文件(.o,二进制)
  4.  链接:形成可执行程序

gcc编译流程原理

(以gcc为例)

预处理

预处理阶段 : 1、去注释 2、头文件的展开 3、宏替换 4、条件编译

预处理执行操作:

gcc -E code.c -o code.i

  • -E : 程序开始翻译 , 做完对源文件的预处理(头文件的展开/宏替换/去注释/条件编译) , 就会停下来! 只做预处理(不编译、不汇编、不链接)
  • -o : GCC编译器的输出文件指定选项,作用是“把预处理的结果保存到后面跟的文件里”。
  • -o code.i 把预处理的结果输出到 code.i 文件中(如果不加 -o ,默认会把结果输出到屏幕上)

下面我们详细的看一下预处理阶段中这个步骤是如何进行的?

宏替换和去注释:

最开始只有左边的code.c文件 , 里面定义了头文件和宏 , 也对一部分内容进行了注释 , 然后执行gcc -E code.c -o code.i 操作进行预处理生成了code.i文件 , 对比两个文件可以明显的观察到头文件 , 宏以及注释部分都不见了(本质上是编译器进行了裁剪) , 这说明了预处理阶段就完成了宏替换、去注释、头文件展开这几件事 , 为后续的编译(转汇编)做准备。

头文件的展开:

打开code.i文件 , 在文件的最上面便是头文件的展开部分 , #include 会嵌入头文件内容 , #include <stdio.h> 会把 stdio.h 的全部内容直接插入到当前文件中——这也是预处理阶段的核心操作之一。本质上是复制头文件 , 把 #include <stdio.h> 这类头文件,复制替换成头文件本身的全部内容。

条件编译:

下面我们对code.c中的代码进行改造 , 以便探究条件编译是如何进行的

上面两幅图完美展示了预处理阶段“条件编译”的逻辑 , 第一幅图当 #define VERSION1 1 存在时 , 预处理阶段会识别到 VERSION1 已定义,因此保留 #ifdef 分支里的代码(“我是version1版本的功能”),同时丢弃 #else 分支的代码。然后使用vim将 #define VERSION1 1 注释掉后再次进行预处理操作并打开code.i文件后 , 就可以看到当 #define VERSION1 1 被注释时 , VERSION1 未定义,预处理阶段会丢弃 #ifdef 分支,只保留 #else 分支的代码(“我是version2版本的功能”)。

条件编译的实质 : 条件编译其实是“在预处理阶段对代码进行选择性裁剪”——它通过宏的定义状态(存在/不存在、值的真假),决定哪些代码段会被保留、哪些会被直接丢弃,最终只让“符合条件”的代码进入后续的编译流程。

编译

编译执行操作:

gcc -S code.i -o code.s

  •  -S :执行编译步骤(把高级语言代码翻译成汇编指令),但不进行后续的汇编和链接
  • code.i :输入文件(预处理后的C代码文件), 编译操作是在上一步预处理操作的基础上进行的
  • -o code.s :指定输出文件为 code.s (汇编语言文件)

当我们打开这个 code.s 汇编语言文件 , 就可以看到里面的汇编代码内容

那在编译阶段为什么不直接把C语言代码直接变成二进制代码呢?而是先将C语言代码变成汇编语言,然后将汇编语言变成二进制代码呢?

最早期人们编程是电路开关,用01指代,再往后就有了汇编语言,从开始有了汇编以后,计算机就有了编译器这个概念,没有C语言时,只有汇编语言和汇编语言编译器时,那时是汇编语言可以直接会翻译成二进制代码,在有了C语言时,把C语言变成二进制代码要比汇编语言变成二进制成本要高的多,为什么呢?因为C语言是接近于人的自然语言,如果直接把这个语言翻译成二进制,翻译成本是非常高的,所以我们只需要将C语言翻译成汇编语言,而汇编语言翻译成二进制代码这部分工作前人已经帮我们做过了,我们不需要做这部分工作,所以我们只需要管将C语言转换为汇编语言即可,成本大大降低,所以就遵守了这个规则,就有了去注释、编译、汇编、链接

Linux几乎可以直接在命令行中执行大部分后端语言:python,java等等,都可以在Linux当中写代码

其实学习语法的本质就是学习编译器的编译规则

汇编

把汇编语言翻译成二进制文件(.o : 可重定位二进制文件)

汇编执行操作:

gcc -c code.s -o code.o

  • -c 执行汇编步骤(把汇编指令翻译成机器能识别的二进制指令),但不进行最后的链接
  • code.s :输入的汇编语言文件(建立在编译的基础上)
  • -o code.o :指定输出为 code.o (可重定位二进制文件,是编译流程中“汇编阶段”的产物)

再次ll时 , 可以看到显示出了上面过程中产生的所有文件

再次打开这个 code.o 可重定位二进制文件时 , 里面就是一堆乱码

链接 

链接执行操作:

gcc code.o -o code

  • 这是编译流程的最后一步将汇编阶段生成的目标文件( code.o )与系统库、其他依赖文件进行链接,最终生成可执行程序(code)
  • code : 生成的可执行程序 , 完成后可通过 ./code 直接运行程序。
  • 若直接执行 gcc code.c (不带选项),GCC会自动完成“预处理→编译→汇编→链接”全流程,默认生成名为 a.out 的可执行文件。

gcc常见的一些选项:

动态链接和静态链接

在链接阶段 , 链接会分为动态链接和静态链接

动态链接:

#include<stdio.h>
#define M 100
int main()
{int i = 0;int sum = 0;for(;i<10;i++){sum+=i;}printf("sum=%d\n",sum);while(i>0){i--;}return 0;
}

比如我们写了这么一段代码,求完一到十的累加,程序执行后会去库里面去找printf函数并使用,使用完之后继续执行下面写的代码,生成的可执行程序只包含我们自己写的代码以及我们要使用的外部函数的链接部分,这就是动态链接的过程

链接的是关联关系 , 更改的是库的地址 , 这个过程需要进行对动态库的跳转

静态链接:

对于上面的代码,我们使用printf函数需要去外部的库里面去找,而静态链接相当于就是将printf函数的代码拷贝到我们的代码里面使用,就不用去外部动态库里面找了

静态链接本质就算将相关库中的代码拷贝到自己的可执行程序里面

一旦链接成功,就不依赖相关库了

我们也可以使用相关的指令对之前最终生成的可执行程序文件进行查看:

  • libc-2.17.so : C语言的标准库文件(包含 printf 、 malloc 等函数的实现):
  • libc 是C标准库的核心文件, 2.17 是版本号;
  • /lib64 是64位系统的库文件路径,编译后的程序会在运行时链接这个库来调用标准函数。
  • 那 .so 时什么呢?   后面会详细介绍
ldd指令
ldd /usr/bin/可执行文件或指令

通过上图 ldd 命令的输出,可以看出:

  1. 这三个指令( ls / which / mkdir )都依赖 libc.so.6 ——它是C标准库的动态版本,提供了printf、内存分配、文件操作等基础功能,几乎所有Linux程序都会依赖它。
  2. 所有输出里都有 /lib64/ld-linux-x86-64.so.2 ——它是x86_64架构Linux的动态链接器,负责在程序启动时,找到并加载这些依赖的动态库。

libc.so.6 是 Linux系统中最核心的动态库之一——GNU C标准库(glibc)的动态链接版本,它的核心作用是提供C语言程序运行所需的基础功能,具体可以拆成这3点:

  1. 基础函数支持:比如 printf (打印)、 malloc (内存分配)、 fopen (文件操作)、字符串处理等C语言标准函数,几乎所有C/C++编写的程序都依赖它。
  2. 系统调用封装:它是程序和操作系统内核之间的“中间层”,比如程序要读取文件,会先调用 libc.so.6 里的 read 函数,再由这个库去触发内核的实际系统调用。
  3. 版本标识: so.6 是它的版本后缀(对应glibc的主版本,比如CentOS 7的 libc.so.6 对应glibc 2.17,Ubuntu 22.04对应glibc 2.35),不同系统的 libc.so.6 版本决定了程序能兼容的功能和运行环境。

简单说:没有 libc.so.6 ,Linux下绝大多数C/C++程序都跑不起来——它是系统程序的“基础运行骨架”。


那 libc.so.6 能删除吗?

它是Linux系统的“命脉级组件”——系统中绝大多数程序(包括 ls 、 cp 、 bash 等基础命令)都依赖它运行。一旦删除:

  1. 所有依赖它的程序会立即无法执行;
  2. 系统会直接崩溃,连基本的命令操作都做不了,甚至无法正常开机。

可以说 libc.so.6 是Linux系统的“地基”,删除它等于直接毁掉系统,千万别尝试。

编译器有了,头文件有了,库文件也有了,这些组件共同构成了C语言的开发与运行环境:编译器负责编译代码,头文件提供函数声明,库文件提供函数实现,三者配合才能完成C程序的编译与运行。所以在Linux操作系统下这三个组件都存在 , 所以在Linux下就能跑C语言了

动态库与静态库

  • Linux下,动态库后缀是XXX.so, 静态库是XXX.a
  • Windows下,动态库是XXX.dll, 静态库是XXX.lib
  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为 “.a”
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 “.so”,如前面所述的libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
  • gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 指令验证。

.so:动态库

dynamically linked表示动态链接     .so表示动态库    gcc在编译时默认使用动态库

.a:静态库 

系统默认没有安装静态库 , 使用下面的操作就可以安装

进行静态链接的操作是:

gcc code.c -o code-static -static

这是用 gcc 以静态链接方式编译 code.c 文件,生成可执行文件 code-static 。其中 -static是关键,它强制编译器将所有依赖的库代码“打包”进最终程序,使其成为静态链接的可执行文件。

上图中的 file 指令是查看 code-static 文件的类型。输出中的 statically linked  明确表明该程序是静态链接的 , 验证了前面静态编译的结果。

动态链接和静态链接的优缺点

静态链接的优缺点
优点:

  • 移植性强:可执行文件包含所有依赖的库代码,复制到其他相同系统环境的机器上就能直接运行,无需担心依赖库缺失问题。
  • 运行时无外部依赖:程序运行时不需要依赖系统中的动态链接库,避免了因动态库版本不兼容或缺失导致的程序崩溃。

缺点:

  • 文件体积大:因为把所有依赖的库代码都打包进了可执行文件,所以文件尺寸通常很大,会占用更多的磁盘空间。
  • 内存占用高:如果多个程序都使用了相同的静态库,每个程序都会在内存中加载一份库代码,造成内存的重复占用。
  • 更新不便:当依赖的库有更新(比如修复了漏洞或增加了新功能),必须重新编译整个程序才能使用新的库版本。

动态链接的优缺点
优点:

  • 文件体积小:可执行文件只包含自身的代码和对动态链接库的引用,所以文件尺寸较小,节省磁盘空间。
  • 内存共享:多个程序可以共享同一份动态链接库在内存中的加载实例,大大节省了内存资源,例如多个应用程序共享系统的C标准库 libc.so 。
  • 更新方便:当动态链接库更新时,只需要替换对应的动态库文件,所有依赖它的程序无需重新编译,就能直接使用新的库功能或修复,比如系统更新了某个图形库,所有依赖该图形库的应用程序都能直接受益。
  • 编译速度快:编译时只需要处理自身的代码,不需要把库代码都整合进来,所以编译过程更快。

缺点:

  • 移植性差:程序运行时依赖系统中的动态链接库,若目标机器上缺少对应的动态库或库版本不兼容,程序就无法运行,比如在Windows上开发的依赖 xxx.dll 的程序,复制到另一台没有该 dll 的Windows机器上就无法运行。
  • 运行时可能出现依赖问题:如果系统中的动态库被错误修改或删除,会导致所有依赖它的程序都无法正常运行。

简单点说 , 静态链接缺点是生成的代码体积大,但是稳定;动态链接优点是生成的代码体积小,但是它依赖第三方库,如果第三方库找不到了,它就会发生错误

http://www.dtcms.com/a/614025.html

相关文章:

  • 如何正确解读央行货币政策数据——以2025年3季度为例
  • 如何做公司的网站建设四川华鸿建设有限公司网站
  • 自助式网站付费电影怎样免费观看
  • 金华网站建设行业济宁网站建设有限公司
  • 不同商用车热管理机组参数
  • [智能体设计模式] 第14章:知识检索(RAG)
  • ps做网站页面设置为多大遵义网站制作一般需要多少钱
  • 鋰電池充電芯片學習
  • Qt Sql 模块中的函数详解
  • 堆的实现与应用:从基础操作到排序与 Top-K 问题
  • Python应用指南:利用高德地图API获取实时天气
  • 移动网站开发教程网站开发有哪些服务
  • 体彩网站建设校园文化建设网站素材
  • Java绘图技术
  • R语言基础(包含资料)
  • 系统思考:打破惯性
  • 数据结构入门 (十一):“自我平衡”的艺术 —— 详解AVL树
  • 网站建设html东莞浩智网站建设多少钱
  • 【工具】文件传输工具_wget·aria2·ssh·scp
  • Python-PDF文件生成水印
  • 站长之家官网查询电子商务网站开发教程论文
  • openGauss:多核时代企业级数据库的性能与高可用新标杆
  • C++ 中dynamic_cast使用详解和实战示例
  • Git笔记---简单介绍与基本使用
  • php网站开发项目经验如何写wordpress是什么软件
  • 手机网站排名优化软件怎么查网站开发者联系方式
  • 菲律宾有做网站的吗人人开发网站
  • 部署Cloudflare免费图床——免费开源强大
  • Vue Router 3 升级 4:写法、坑点、兼容一次讲透
  • JSP 、JSTL、MVC分层思想——以登录验证为例