【Linux编译器gcc/g++】带你了解代码是如何变成可执行程序的!

news2024/11/25 14:35:23

Linux编辑器gcc/g++的使用

  • 背景知识
  • gcc的使用
    • 预处理(进行宏替换)
    • 编译(生成汇编)
    • 汇编(生成及其可识别代码)
    • 链接(生成可执行文件或者库文件)
    • 动态库vs静态库
  • debug和release
  • 🍀小结🍀

🎉博客主页:小智_x0___0x_

🎉欢迎关注:👍点赞🙌收藏✍️留言

🎉系列专栏:Linux入门到精通

🎉代码仓库:小智的代码仓库

背景知识

我们要知道一个写一段代码到可执行程序是需要经过下面四个过程。

  1. 预处理(宏定义替换)
  2. 编译(生成汇编)
  3. 汇编(生成机器可识别代码)
  4. 链接(生成可执行文件或者库文件)

本篇将带大家深度探索以上的四个过程。因为gcc和g++的功能选项是一样的,本篇主要详解gcc的过程。

gcc的使用

我们使用vim编写一个.c文件>
在这里插入图片描述
gcc的使用方法:gcc + .c文件
在这里插入图片描述
编译完成后会生成一个a.out文件,可以看到他的文件权限中是有x(可执行权限)权限的。
我们可以输入这行指令来执行:./a.out
在这里插入图片描述
这样就可以执行我们的C语言程序了。
也可以使用gcc -o 可执行程序 .c文件或者gcc .c文件 -o 可执行程序这两个可以指定生成可执行程序的名称。

当然gcc也是可以帮我们检查语法错误的>
在这里插入图片描述
这里我我们故意不在后面不加;来使用gcc编译。
在这里插入图片描述
可以看到下面给我们报错显示}后面没有';'.

预处理(进行宏替换)

  • 去注释
  • 头文件展开
  • 条件编译
  • 宏替换
    我们继续使用mycode.c文件>
    在这里插入图片描述
    可以看到我们的代码中有注释也有#define M 100定义的宏
    为了让程序执行到预处理阶段我们可以使用以下指令:
    gcc -E mytest.c -o mytest.i
    选项"-E",该选项的作用是让 gcc 在预处理结束后停止编译过程。
    选项"-o"是指目标文件,".i"文件为已经过预处理的C原始程序。

    在这里插入图片描述
    可以看到我们生成了一个mytest.i文件:
    我们来打开mytest.i文件>
    在这里插入图片描述
    使用shift+g跳到末尾行。
    我们可以看到文件变成了850行,这是因为头文件的展开预处理阶段自动把我们所包含的头文件添加进去。mytest.i文件上面的内容都是stdio.h中的内容。
    我们来对比以下两个文件其他的变化。
    在这里插入图片描述
    可以看到mytest.i文件中注释被去掉了,宏定义的M被直接替换为了100
    这里就可以理解为什么宏定义不能进行调试。因为宏定义在预处理阶段就被直接替换掉了。
    我们再来添加一段条件编译>
    在这里插入图片描述
    我们再次对这个文件进行预处理:
    在这里插入图片描述
    再来对比两个文件的区别>
    在这里插入图片描述
    此时可以看到预处理之后是只有else中的语句。
    我们可以通过文件内添加,也可以通过gcc命令行来添加使用这行指令:
    gcc -E mycode.c -o mycode.i -DDEBUG
    在这里插入图片描述
    再来打开两个文件进行对比>
    在这里插入图片描述

这里就可以发现预处理保留了我们if条件下的语句。

编译(生成汇编)

  • 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。

我们可以通过这个指令对mycode.i文件进行编译:gcc -S mycode.i -o mycode.s
-S的功能是从现在进行程序的翻译,将编译工作做完就停下来。
在这里插入图片描述
这里就生成了mycode.s文件。我们再来打开这个文件>
在这里插入图片描述

汇编(生成及其可识别代码)

  • 汇编阶段是把编译阶段生成的".s"文件转成目标文件。
  • 读者在此可使用选项"-c"就可看到汇编代码已转化为".o"的二进制目标代码了。

我们可以使用这行指令进行汇编处理:gcc -c mycode.s -o mycode.o
在这里插入图片描述
再来打开mycode.o文件(注意这里需要用od来查看文件中的内容,因为".o"文件中都是二进制机器可识别的)直接输入指令:od mycode.o
在这里插入图片描述
打开之后我们可以发现这里面都是二进制数,因为机器只认识二进制数(0和1)。

mycode.o可重定位目标二进制文件,简称目标文件,(vs环境下是.obj)
不可以独立执行,虽然已经是二进制了需要经过 链接(库) 才能执行。

链接(生成可执行文件或者库文件)

  • 在成功编译之后,就进入了链接阶段。
    这里我们在使用 :gcc -o mytest mycode.o
    在这里插入图片描述
    这里就生成了我们自定义的名称的可执行程序mytest
    再来执行这个可执行程序:./mytest
    在这里插入图片描述
    gcc -o mytest mycode.o是将可重定位目标二进制文件,和进行链接形成可执行程序

在 Linux 中,库(Library)是一组可重用的代码和数据,它们提供了一些常用的函数和方法,可以被其他程序调用。库通常被编写成共享对象文件(Shared Object File),也称为动态链接库(Dynamic Linking Library),这种文件可以在运行时被加载到内存中,并被多个程序共享。

Linux 中的库可以分为两类:

  1. 静态库(Static Library):静态库是一组预编译的对象代码,它们被链接到目标程序中,使得目标程序包含了所有所需的代码和数据。静态库的文件扩展名通常是 .a.lib。静态库的优点是使用简单,因为它们被编译到目标程序中,所以不需要在运行时加载库文件。但缺点是占用更多的磁盘空间。

  2. 动态库(Shared Library):动态库是一种动态链接库,它们被多个程序共享。共享库的文件扩展名通常是 .so.dll。共享库的优点是占用更少的磁盘空间,并且可以在多个程序之间共享代码和数据。但缺点是需要在运行时加载库文件,因此会稍微降低程序的执行速度。

Linux 中有许多常用的库,例如 C 语言标准库 libc、数学库 libm、图形库 libX11 等等。这些库通常都已经安装在系统中,可以直接使用。同时,开发者也可以编写自己的库,并将其发布供其他程序使用。

在这里插入图片描述
我们可以使用:ldd+可执行程序来查看可执行程序所依赖的库:
在这里插入图片描述
libc.so.6 => /lib64/libc.so.6是C语言的动态库。
我们也可以使用ldd来查询Linux中的指令。
在这里插入图片描述
可以看到ls指令也是依赖C语言动态库的,这是因为Linux中大部分指令都是使用C语言编写的。
我们再来使用静态库来链接一下:gcc -o mytet_static mycode.s

可以看到动态库和静态库链接形成的两个可执行程序的大小差距还是很大的,因为静态库是将其所有内容全部拷贝过去,所以所占的字节数要大一点。(平时我们很少会使用静态库链接。)
Linux操作系统中默认是没有静态库的,我们可以使用这行指令进行安装:
sudo yum -y install glibc-static C语言静态库安装
sudo yum install -y libstdc++-static c++静态库安装

【注意】:

  1. 在编译器使用 静态库进行静态链接的时候,会将自己的方法拷贝到目标程序中,该程序以后不用再依赖静态库!
  2. 动态库不能缺失,一旦对应的动态库缺失,影响的不止一个程序可能导致很多程序都无法进行正常运行!
  3. 在Linux中,编译形成可执行程序,默认采用的就是动态链接–提供动态库
  4. 在Linux中,如果要按照静态链接的方式,进行形成可执行程序,需要添加-static选项–提供静态库

如果我们没有静态库,但是我们就要-static,行不行呢? 答案是肯定不行
如果我们没有动态库,只有静态库,而且gcc能找到 - 能的,gcc默认优先动态链接-static的本质: 改变优先级
不一定是纯的全部动态链接或者静态链接,混合的!
加了-static所有的连接要求全部变成静态链接(只适配一次)

我们也可以通过file指令来查询可执行程序到底依赖的是哪一个库:
在这里插入图片描述

动态库vs静态库

动态库和静态库都有各自的优缺点,下面是它们的主要特点:
动态库的优点:

  • 节省内存:多个程序可以共享同一个动态库,避免了重复加载代码和数据的浪费。
  • 灵活性:动态库可以在运行时动态加载和卸载,使得程序更加灵活,可以根据需要加载不同的库版本。
  • 兼容性:动态库可以在不同的平台上运行,因为库文件和程序是分离的,不需要重新编译程序。

动态库的缺点:

  • 性能:由于需要在运行时加载库文件,所以动态库会稍微降低程序的执行速度。
  • 稳定性:如果动态库的版本不兼容,或者动态库本身存在漏洞,可能会导致程序崩溃或者安全问题。

静态库的优点:

  • 稳定性:静态库可以使得程序更加独立和稳定,因为程序不依赖于外部的库文件。
  • 性能:由于每个程序都包含完整的代码和数据,所以静态库可以提高程序的执行速度。
  • 安全性:静态库不会受到动态库版本兼容性问题的影响,因此更加安全。

静态库的缺点:

  • 浪费内存:每个程序都需要包含完整的代码和数据,因此静态库会占用更多的磁盘空间和内存。
  • 不灵活:如果需要更新静态库的版本,那么需要重新编译程序,并重新分发给用户。

小结:

  1. 动态库因为是共享库,有效的节省资源(磁盘空间,内存空间,网络空间等)[优]动态库一旦缺失,导致各个程序都无法运行[缺点]
  2. 静态库,不依赖库,程序可以独立运行[优点],体积大,比较消耗资源[缺]

debug和release

在Linux中,Debug和Release是两种不同的编译模式,其主要区别在于编译器优化和符号表信息。

Debug模式:

  • 编译器不会对代码进行优化,生成的可执行文件包含完整的符号表信息,以便调试程序时能够进行源代码级别的调试。
  • 可执行文件的体积通常比较大,因为包含了完整的符号表信息和调试相关的代码。
  • Debug模式通常用于开发阶段,方便开发人员进行调试和定位问题。

Release模式:

  • 编译器会对代码进行优化,以提高程序的执行效率和减小可执行文件的体积。
  • 可执行文件不包含完整的符号表信息,因此不能进行源代码级别的调试。
  • Release模式通常用于发布阶段,生成最终的产品版本。

我们默认编译生成的是release版本,如果我们想要生成debug版本可以添加一个-g选项: gcc -o mytest_debug mycode.s
在这里插入图片描述
我们可以看到debug版本比release版本所占的字节数是要大一点点的。

🍀小结🍀

今天我们学习了Linux编译器gcc/g++的使用相信大家看完有一定的收获。
种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608709.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FPGA零基础学习之旅#6】ip核基础知识之计数器

🎉欢迎来到FPGA专栏~ip核基础知识之计数器 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹 ✨博客主页:小夏与酒的博客 🎈该系列文章专栏:FPGA学习之旅 文章作者技术和水平有限,如果文中出现错误,希望大家…

[网络工程]小型局域网组建的常用命令(ENSP)

⭐作者介绍:大二本科网络工程专业在读,持续学习Java,努力输出优质文章 ⭐作者主页:逐梦苍穹 目录 1、引言2、常用命令(ENSP)常规VTYConsole端口安全单臂路由DHCPNATACL基础ACL高级ACL GVRPSTP 1、引言 局域网(Local A…

C++学习——第一节课-初识C++

大家好,我是涵子。今天我们来开始学习C。 目录 一、课前准备 二、C的第一个程序 2.1.C是个啥 2.2.C的第一个程序编写 2.2.1.头文件 2.2.2.命名空间 2.2.3.主程序函数 2.2.4.输出流 2.2.5.代码结束 三、其它的应用 3.1.输出三角形,矩形和勾 …

单例模式C++实现和观察者模式C++实现

目录 1、单例模式介绍 2、单例代码实现 2.1 static介绍 2.2 C中static的三种用法: (1)静态局部变量 (2)静态成员变量 (3)静态成员函数 3、观察者模式介绍 4、观察者代码实现 1、单例模…

.ini配置文件介绍与解析库使用

【前言】 ini 文件是英文"Initialization"的缩写,即初始化文件。它用来配置特定应用软件以实现对程序初始化或进行参数设置。.ini文件由节(section)、键(key)、值(value)三种模块构成。在windows系统/嵌入式软件中有很多XXX.ini文件,例如Syste…

IDC机房相电压与线电压的关系

380V电动机(三相空调压缩机)的电流计算公式为:Ⅰ=额定功率(1.732额定电压功率因数效率)。 功率因数是电力系统的一个重要的技术数据。功率因数是衡量电气设备效率高低的一个系数。功率因数低,说…

通过源码编译安装搭建 LNMP平台

搭建LNMP平台 一. 安装Nginx服务1.1 安装依赖包1.2 创建运行用户1.3 编译安装1.4 优化路径1.5 添加 Nginx 系统服务 二. 安装mysql服务2.1 安装Mysql环境依赖包2.2 创建运行用户2.3 编译安装2.4 修改mysql 配置文件2.5 更改mysql安装目录和配置文件的属主属组2.6 设置路径环境变…

VMware安装Windows11

VMware安装Windows11 嘚吧嘚准备工作VMware下载Windows11下载 VMware安装Windows11VMware配置安装Windows11 嘚吧嘚 最近在搞一些自己感兴趣的东西,需要(临时)安装一些软件来验证,考虑到用完还要卸载,不想把自己的电脑搞得乱七八糟&#x1f…

字节序及IP地址转换

一、主机字节序和网络字节序 1.什么是字节序? 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序,分为:大端字节序(Big endian)、小端字节序(Little endian)。 示例&am…

前端技术双周刊 2023-06-04:React 发布 10 周年整

项目地址:olivewind/weekly 微信公众号:依赖注入 发布时间:2023.06.04 本周内容:资讯x3、开源x4、文章x5 动态 React 圆桌会议 - Server Components, Suspense 和 Actions 为庆祝 React 成立 10 周年,Delba de Oliveir…

MySQL 约束控制

文章目录 约束控制非空约束主键约束默认值约束唯一约束外键约束 约束控制 数据的完整性约束(简称“约束”)是在表和字段上强制执行的数据检测规则,是为了防止不规范的数据进入数据库。当我们对数据进行 DML 操作时,数据库管理系统…

被面试官上过一课后,我学到的不止是如何答题

写在前面双向奔赴切勿急于否定自己做足功课关于简历关于简历投递常见面试题汇总● 按照一般的面试流程,先来一个自我介绍吧● 你的优点和缺点是什么● 你理解的项目经理是干什么的● 能完整的说一下软件项目的整个流程么● 项目经理和产品经理的区别在哪里● 项目管…

UniAD:实现多类别异常检测的统一模型

来源:投稿 作者:Mr.Eraser 编辑:学姐 论文标题:用于多类异常检测的统一模型 论文链接:https://arxiv.org/abs/2206.03687 论文贡献: 提出UniAD,它以一个统一框架完成了多个类别的异常检测。 …

Jetson 硬件 安装SSD固态作为启动盘以及安装CUDA等

Jetson硬件的自带闪存一般较小,只能安装jetpack等基本的环境,所以需要额外增加SSD固态或SD卡作为存储空间,很明显SSD的读取速度远远大于SD卡,所以为更好发挥出Jetson 的计算性能,我们选择使用SSD固态作为存储 1. 安装…

随机森林原理和性能分析

文章目录 随机森林入门构造随机森林随机森林性能随机森林特点 随机森林入门 决策树入门、sklearn实现、原理解读和算法分析中针对决策树进行了详细的描述,但是其只考虑了一颗决策树的情况。俗话说,三个臭皮匠,顶个诸葛亮。本文将研究如何通过…

C++:深入理解多态,多态实现原理及拓展

文章目录 1. 理解虚表1.1 虚表1.2 验证1.3 子类虚表1.4 相同类不同对象的虚表 2. 静态绑定和动态绑定2.1 静态绑定2.2 动态绑定 3. 多态的实现原理3.1 向上转型3.2 多继承3.3 原理 4. 拓展4.1 构造函数能不能是虚函数4.2 父类和子类的析构函数在底层的命名问题4.3 对象之间无法…

[论文阅读] (30)李沐老师视频学习——3.研究的艺术·讲好故事和论点

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正,非常欢迎大家给我留言评论,学术路上期…

SpringMVC第十一阶段:SpringMVC 拦截器执行源码解析

SpringMVC 拦截器执行源码解析: 1、执行doDispatcher做请求分发处理 1.1、调用getHandler()获取请求处理器,处理器中包含请求的方法和拦截器信息 getHandlerInternal() 根据请求地址获取对应的目标方法getHandlerExecutionChain() 获取请求地址对…

(转载)基于鱼群算法的函数寻优算法(matlab实现)

1 理论基础 1.1 人工鱼群算法概述 人工鱼群算法是李晓磊等人于2002年提出的一类基于动物行为的群体智能优化算法。该算法是通过模拟鱼类的觅食、聚群、追尾、随机等行为在搜索域中进行寻优,是集群体智能思想的一个具体应用。生物的视觉是极其复杂的,它…

Java006——对第一个Java程序HelloWorld的简单介绍

一、HelloWorld.java程序整体认识 public class HelloWorld { //创建一个名字叫HelloWorld的类(Java中的类叫class)public static void main(String[] args) {//主程序入口,类似C语言main函数System.out.println("He…