初探——【Linux】程序的翻译与动静态链接

我们所写的C/C++程序计算机是看不懂的,它只认识0101这样的机器码。所以我们就需要借助编译器对这些源代码进行翻译,使之成为计算机能够执行的二进制指令。这个过程通常分为几个关键步骤:预处理、编译、汇编和链接。

一.预处理(Preprocessing)

预处理是翻译的第一步,编译器会对代码进行修改整理,进行替换工作,或者删除一些无用代码。它会进行以下几个步骤:

  • 宏替换:将源代码中定义的宏用其对应的值或者代码块进行替换,这只是简单的文本替换
  • 头文件展开:将源代码中的头文件在包含位置进行展开。这个过程是递归式,即包含的头文件内可能还包含其他的头文件。
  • 条件编译:根据条件编译有选择地编译满足条件的代码,不满足的代码直接被编译器删除
  • 去注释:删除源代码中所有的注释信息

 我们可以借助linux上的gcc编译器的各种选项来控制翻译的过程:

gcc -E test.c -o test.i

-E选项,让程序进行了预处理之后停下来,然后生成对应的.i文件

 我们看下图,右侧是我们所写的源代码,左侧是经过预处理之后的代码,首先能感受到的就是预处理之后的代码量比我们的.c大的多,这主要就是因为头文件展开导致的。还有就是注释的删除以及条件编译。

 二.编译(Compilation)

编译这个过程是将我们经过预处理之后的代码进行一系列操作将其翻译为汇编代码。这个过程编译器对源代码进行词法分析、语法分析和语义分析,以确保代码的正确性,并将其转换为等价的汇编代码。

  • 词法分析:将源代码中的字符流分割成一系列记号(如关键字、标识符、字面量、特殊字符等)。
  • 语法分析:根据编程语言的语法规则,将词法分析阶段产生的记号组织成语法树。
  • 语义分析:对语法树进行语义检查,如类型匹配、类型转换等,并生成中间代码或汇编代码。

编译之后,会生成一个.s文件

Linux上的gcc编译器可以通过-S选项,使源代码只进行到编译过程,然后生成其对应的.s文件:

gcc -S test.i/test.c -o test.s 

既可以从.c源代码进行编译,编译完后停止,也可以从预处理之后的文件.i直接开始编译

 最左侧的代码就是经过编译之后的汇编代码:

 汇编代码是一种非常底层的代码,那么为什么要将我们的源代码编译成汇编代码呢?

这就与计算机的发展历史有关了,计算机刚发展出来是通过开关给计算机输入01这样的二进制指令,使其进行计算。接着就有了打孔编程,根据01的位置关系进行打孔,再将该纸条放入计算机的输入端,通过一些特殊操作使计算机读取数据。

但是上面两种方式都太麻烦了,随着时间的推演,汇编语言诞生了,大家都开始使用汇编语言编写代码,与此同时,汇编代码的产生也就促进了编译器的诞生。因为汇编代码计算机不认识,只能借助编译器对其进行翻译。

但毕竟汇编是非常底层的代码,写起来很难,于是便有了基于汇编代码产生的C语言。但是C语言计算机也无法直接执行,所以就需要新的编译器将C语言代码进行翻译。那此时翻译还需要直接将C语言翻译成01的二进制指令嘛?当然不需要了,我们已经有了汇编->二进制的基础了,我们直接将C->汇编不就行了。

所以将源代码首先翻译成汇编指令是历史的原因,这样可以加快语言的发展,不必埋头解决翻译难题了。

三.汇编(Assembling)

汇编是将汇编代码转换为机器代码(二进制指令)的过程。汇编器读取汇编代码文件,并将其转换为计算机可以直接执行的机器指令。

汇编阶段的主要任务是:

  • 指令翻译:根据汇编指令和机器指令的对照表,将汇编指令一一翻译为对应的机器指令。
  • 生成目标文件:将翻译后的机器指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件中(在Windows下目标文件是.obj,在Linux下目标文件为.o)。

汇编完成后,会生成一个扩展名为“.o”的目标文件。

linux中可以借助gcc编译器的-c选项,使代码的翻译过程在完成汇编之后停下来,生成对应的.o文件。

gcc -c test.c/test.i/test.s -o test.o

gcc -c test.c

gcc会根据第二个指令,直接生成同名.o文件

如下,就是经过汇编之后的二进制文件

 但是生成的二进制文件还不能直接被执行,因为里面缺少方法的实现,所以还需要最后一步——链接

四.链接(linking)

链接是将多个目标文件以及所需的库文件合并成一个可执行程序的过程。链接器负责解决目标文件之间的符号引用问题,并将它们组合成一个统一的可执行文件。

链接阶段的主要任务包括:

  • 地址和空间分配:为每个目标文件中的代码和数据段分配内存地址。
  • 符号决议:解决目标文件之间的符号引用问题,即将一个文件中引用的符号与其在另一个文件中的定义连接起来。
  • 重定位:修改目标文件中的指令和数据,以便它们指向正确的内存地址。

链接完成后,会生成一个可执行程序文件(在Windows下通常为.exe文件,在Linux下默认为具有可执行权限的a.out文件,但也可以通过编译器选项自定义输出文件名)。

gcc test.c/test.i/test.s/test.o -o test

可执行程序名可以任意指定,如果没有指定的话默认是a.out

我们可以看到,经过链接之后产生的可执行程序的容量又大了很多,这就是链接过程将该程序所依赖的方法的实现引入到了文件中。 如果引入的是方法的地址——动态链接,如果将方法拷贝到了目标文件中——静态链接。

 那么有个疑问,我们已经包含了对应方法的头文件,为什么还要进行链接操作呢?

因为头文件中只是包含了对应方法的声明,我们在使用该方法的话程序需要跳转到该方法的具体实现才可以。

五.动态链接和静态链接

链接的过程是将库中的方法的实现和我们的.o结合的过程。

那什么是库呢?

库是一套方法或者数据集,它可以提高我们的开发效率。之所以会出现库就是因为在开发过程中,大家都发现一些的方法函数会被频繁使用,但是没有库的话,就需要自己实现,而且1000个人就有1000种实现方法。

所以人们就设计出来一套库,里面包含了各种常见的方法容器等等。比如我们的c标准库。

那么是什么动态链接什么是静态链接呢?

其实很好理解链接动态库就是动态链接,链接静态库就是静态链接。在windows下,动静态库的后缀分别为.dll,.lib,在linux下,动静态库的后缀分别为.so,.a.

动态链接其实是将动态库中的方法的地址给到源代码中调用该方法的位置。当程序运行之后,就可以根据这个地址跳转到对应的方法,执行完方法之后,在跳回到调用方法的位置继续执行代码。所以在动态链接的过程中,需要全程依赖动态库,动态库丢失就会导致程序运行失败。

静态链接则是将静态库中该方法拷贝到源代码中,程序执行过程直接在源代码中执行,且连接之后程序不再需要静态库。

linux中可以借助ldd命令查看可执行程序依赖了那个共享库,还可以借助file命令查看该可执行程序是采取了哪种链接方式:

如下图我们可以到看,该test可执行程序依赖c标准库,且采取的是动态链接。 

 那么我们要是就想让程序进行静态链接呢?

gcc test.c -o test_static -static
我们在编译的时候可以加上-static选项,使其进行静态链接

因为是静态链接,所以不会依赖任何共享库 

 那么动态链接和静态链接的结果有什么不同嘛?两者有什么有缺点嘛?

我们从链接的方式就可以体现第一个差别:动态链接只给了地址,而静态链接需要拷贝方法,所以动态链接的可执行程序体积小,静态链接的可执行程序体积大

程序运行,需要将代码加载到内存中,静态链接会加载很多的重复代码到内存中,浪费资源。

静态链接的程序对静态库的依赖度低,动态链接则动态库不能丢失。

静态链接是拷贝静态库中的方法到源代码中,所以链接之后静态库就没用了;而动态链接则是通过地址寻址到动态库中的方法,所以动态库不可缺失。

对于动态库来说,如果多个源代码中包含了同一种方法,等到程序加载到内存中时,内存中只有一份方法 。

 六.多文件编译

在大型项目中,我们会包含都源文件。有些是运行逻辑,有些是方法实现。那么要怎么编译这个项目呢?

 我们可以直接编译所有的源文件,生成一个可执行程序

但是更常用的是,我们先分别将所有的源文件都编译成.o为后缀的目标文件,最后在进行动态链接的时候将所有的.o文件一起链接成一个可执行程序

 那么为什么要先生成目标文件,最后一起进行链接呢?

在项目结束后,我们最终会公开我们的头文件,里面包含了方法的声明以及各种接口。但是用户只有声明是无法进行编译的,所以我们还得提过实现,但是如果我们直接提供.c源文件,那不就等于泄露了机密嘛。

所以解决办法就是将所有的.c源文件先进行编译,生成对应的.o文件,最后将所有的目标文件打包放在一个公共的文件里使之成为一个库,最终我们将头文件以及这个包含了目标文件的库交给用户,它们需要某个方法时可以直接链接该库,这下就不害怕泄密了。

其实我们用的c标准库也是如此,里面都是.o目标文件所组成的。 


完~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

亲测有效!如何快速实现 PostgreSQL 数据迁移到 时序数据库TDengine

小T导读:本篇文章是“2024,我想和 TDengine 谈谈”征文活动的优秀投稿之一,作者从数据库运维的角度出发,分享了利用 TDengine Cloud 提供的迁移工具,从 PostgreSQL 数据库到 TDengine 进行数据迁移的完整实践过程。文章…

matlab实现数据极坐标显示

%% % 读取文件数据 filename E:\ProjectWorkspace\866\866data\665hangji.txt;%代码 距离 方位相对正北 时间 地址 横滚角度 TRK importdata(filename); filename1 E:\ProjectWorkspace\866\866data\665dianji.txt;%代码 距离 方位相对正北 时间 地址 横滚角度 PLOT …

Jenkins 启动

废话 这一阵子感觉空虚,心里空捞捞的,总想找点事情做,即使这是一件微小的事情,空余时间除了骑车、打球,偶尔朋友聚会 … 还能干什么呢? 当独自一人时,究竟可以做点什么,填补这空虚…

人工智能之深度学习_[4]-神经网络入门

文章目录 神经网络基础1 神经网络1.1 神经网络概念1.1.1 什么是神经网络1.1.2 如何构建神经网络1.1.3 神经网络内部状态值和激活值 1.2 激活函数1.2.1 网络非线性因素理解1.2.2 常见激活函数1.2.2.1 Sigmoid 激活函数1.2.2.2 Tanh 激活函数1.2.2.3 ReLU 激活函数1.2.2.4 SoftMa…

卸载和安装Git小乌龟、git基本命令

卸载 Git 打开控制面板: 按 Win R 打开运行对话框,输入 control 并按回车键。或直接在功能搜索里搜索“控制面板”。在控制面板中,选择“程序”或“程序和功能”。 查找并卸载 Git: 在程序列表中找到“Git”或“Git for Windows…

群论学习笔记

什么是对称? 对称是一个保持对象结构不变的变换,对称是一个过程,而不是一个具体的事物,伽罗瓦的对称是对方程根的置换,而一个置换就是对一系列事物的重排方式,严格的说,它也并不是这个重排本身…

C语言自定义类型:结构体

结构体简介: c语言里int 、float、double、等等类型来表示一个对象,但有时也有未能表达的对象,比如表示一个人的类型,这个类型里有人的身高、体重、年龄等等,这就需要很多个类型来拼凑,这就很不方便。于是…

【整体介绍】

ODO:汽车总行驶里程 Chime: 例如安全带没系的报警声音 多屏交互就是中控屏的信息会同步到主驾驶的仪表盘上 面试问题:蓝牙电话协议HFP 音乐协议A2DP 三方通话测试的逻辑

线性规划:机器学习中的优化利器

一、线性规划的基本概念 线性规划(Linear Programming, LP)是运筹学中数学规划的一个重要分支,用于在一组线性不等式的约束条件下,找到线性目标函数的最大值或最小值。其问题可以表述为: 在一组线性约束条件 s.t.&am…

SurgiTrack:外科手术视频中的细粒度多类别多工具跟踪|文献速递-视觉大模型医疗图像应用|文献速递-视觉大模型医疗图像应用

Title 题目 SurgiTrack: Fine-grained multi-class multi-tool tracking in surgical videos SurgiTrack:外科手术视频中的细粒度多类别多工具跟踪 01 文献速递介绍 手术器械跟踪在计算机辅助手术系统中发挥着至关重要的作用,可为一系列应用提供有价…

亚博microros小车-原生ubuntu支持系列:1 键盘控制

背景:电脑配置不太行,我在ubuntu再运行vmware,里面运行亚博官方的虚拟机镜像ubuntu,系统很卡。基本上8G内存给打满了。还是想把亚博官方的代码迁移出来,之前售后就说除了官方镜像虚拟机,需要自己摸索迁移。…

总结5..

#include<stdio.h> struct nb {//结构体列队 int x, y;//x为横坐标&#xff0c;y为纵坐标 int s, f;//s为步数&#xff0c;//f为方向 }link[850100]; int n, m, x, y, p, q, f; int hard 1, tail 1; int a[52][52], b[52][52], book[52][52][91]; int main() { …

鸿蒙系统 将工程HarmonyOS变成OpenHarmony

DevEco Studio软件创建工程后需要修改两个地方&#xff1a; 修改第二个build-profile.json5文件 将原先内容&#xff1a; {"app": {"signingConfigs": [],"products": [{"name": "default","signingConfig": &q…

什么样的问题适合用递归

递归是一种通过函数调用自身来解决问题的方法。递归适用于那些可以被分解为相似子问题的问题&#xff0c;即原问题可以通过解决一个或多个更小规模的同类问题来解决。递归通常需要满足以下两个条件&#xff1a; 递归基&#xff08;Base Case&#xff09;&#xff1a;问题的最简…

C# 网络协议第三方库Protobuf的使用

为什么要使用二进制数据 通常我们写一个简单的网络通讯软件可能使用的最多的是字符串类型&#xff0c;比较简单&#xff0c;例如发送格式为(head)19|Msg:Heart|100,x,y,z…&#xff0c;在接收端会解析收到的socket数据。 这样通常是完全可行的&#xff0c;但是随着数据量变大&…

认识BOM

BOM 弹出层 可视窗口尺寸 屏幕宽高 浏览器内核和其操作系统的版本 剪贴板 是否允许使用cookie 语言 是否在线

国产编辑器EverEdit - 大纲视图

1 大纲视图 1.1 应用场景 在编辑较长代码文件时&#xff0c;使用大纲视图可以方便的检视当前文件的变量、函数等信息&#xff0c;方便在不同函数间跳转&#xff0c;对整个文档的全貌了然于胸。   在编辑XML文档时&#xff0c;通过展示XML文件的层次结构、节点布局&#xff0…

(2)STM32 USB设备开发-USB虚拟串口

例程&#xff1a;STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程&#xff0c;没有知识&#xff0c;全是实操&#xff0c;按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的&#xff0c;如果代码中出现一些外设的…

68,[8] BUUCTF WEB [RoarCTF 2019]Simple Upload(未写完)

<?php // 声明命名空间&#xff0c;遵循 PSR-4 自动加载规范&#xff0c;命名空间为 Home\Controller namespace Home\Controller;// 导入 Think\Controller 类&#xff0c;以便扩展该类 use Think\Controller;// 定义 IndexController 类&#xff0c;继承自 Think\Control…

AutoGen入门——快速实现多角色、多用户、多智能体对话系统

1.前言 如https://github.com/microsoft/autogen所述&#xff0c;autogen是一多智能体的框架&#xff0c;属于微软旗下的产品。 依靠AutoGen我们可以快速构建出一个多智能体应用&#xff0c;以满足我们各种业务场景。 本文将以几个示例场景&#xff0c;使用AutoGen快速构建出…