LINUX入门篇【5】----程序的翻译过程解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: LINUX入门篇【5】----程序的翻译过程解析

前言:

在C语言阶段我们已经讲过程序的翻译过程,我们知道程序是由预处理,编译,汇编,链接四部分组成的,但是,当时受到C语言编译器的限制,我们没法去深刻体会这个过程,所以,本次我们利用LINUX再一次更为详细的展现一下程序的翻译。

1.编译器:

不同的语言需要由不同的编译器来进行编译,我们一般使用gcc来编译C语言,g++来编译C++语言,且这个对应是不能混淆的,否则编译是无法通过的。

在这里补充一下C++文件的几种格式:.cpp,.cc,.cxx等,我们不能仅仅认识cpp后缀的叫C++文件,要知道C++后缀的文件不只一种

在LINUX中gcc是自带的,想要编译C++语言,需要我们下载一个g++编译器,在这里我们可以使用:

yum install gcc-c++,也就是利用我们上节课讲的yum软件搜索系统来下载

但是,说到这里,你或许会问,LINUX不是不看后缀么?

这个之前我是说过的,LINUX的确是不看后缀的,但是编译工具是看后缀的,所以我们规范文件名和代码还是很有必要的

2.程序的翻译过程详解:

先让我们复习一下程序的翻译的过程:预处理,编译,汇编,链接,**每经过一个阶段,我们所写的程序就被处理为一个不同的形式,但它的趋势是逐渐处理为我们看不懂而计算机硬件能看懂的过程。**这句话很关键,但为什么要这样去一步一步的去翻译呢?难道直接一步到位让计算机看懂不行么?

这就要谈一谈我们程序的发展过程了:

首先我们明白,计算机的本质是一堆硬件,只能处理二进制信息,不管是多么强大和先进的计算机,它都只能识别二进制的数据,因此,早期的科学家们使用打孔的方式打印计算机信息,后来经过发展,人们发明了一套编码负责专门处理这种二进制信息,这便是汇编语言,但是,就像我说过的,计算机只能看懂二进制信息,因此,需要专门把汇编语言翻译成二进制信息给计算机看,由此,便诞生了最早的编译器,汇编语言编译器,负责将汇编代码翻译成二进制语言交给计算机去理解。但是,即使是汇编语言依旧晦涩难懂,大部分人没法快速掌握语法和功能,由此,科学家们又以汇编语言为底层,开发出了更高级的语言—C语言!同时,C语言的编译器也应运而生,但和汇编编译器不同,C语言的编译器负责去将C语言的代码翻译成汇编语言,然后汇编语言再经过编译器变成二进制代码。从这之后,C++诞生了,但是其本质都不无承接C语言的做法,将其翻译成汇编语言。C/C++之后,又出现了一大批以C/C++为底层的解释性语言,JAVA python php他们的本质是解释代码的意思而不是编译,但是你可以理解为他们是将其代码解释成汇编代码,因此他们使用解释器而不是编译器。通过这个过程,我i们发现,语言的发展是一层接着一层去承接的,下一层把上一层当作底层来进行翻译,这个过程便是语言/编译器的自举过程。

这也就解释了为什么我们的计算机程序翻译是一步一步进行的而不是跨越,因为每一步都有对应的编译器,你总不能重新构建一个由C语言直接翻译为二进制代码的编译器吧,那样需要消耗大量的成本和时间,而且毫无意义,这样一层一层的编译,提高了效率也节省了成本。

由此,我们便先解开了为什么我们的计算机程序要一步一步去翻译的问题,下面就让我们一步一步去看:

1.预处理:

在预处理阶段,由C语言那篇文章我们知道,主要进行的是4个过程:

1.头文件展开
2.去掉注释
3.条件编译
4.宏替换

经过这4个过程,我们的程序代码就更加纯粹和简洁,我们以LINUX我写的这个代码为例子,来讲一讲。

这是一个很简单的C语言程序,在这里包括条件编译,宏,条件编译,注释,让我们看看经过预处理阶段后程序会变成什么样子呢?

我们wq退出程序,然后我们输入指令gcc -E 文件名 -o 新的文件名

这条指令的意思就是:从现在开始进行程序的翻译,到预处理完就停下来,并将预处理完的结果放入我们后面的文件中,如下:

我将文件放入到code.i文件中,现在让我们打开文件:

是的,你会发现宏被替换后消失了,同时条件编译直接保留条件编译要执行的程序部分,其他部分全部剪切掉,同时注释被取消,这样,我们的代码就变得更加简洁。

条件编译为我们提供了一个很好的思路:

通过条件编译,我们是可以实现代码的动态裁剪的,比如,就如同我们玩的游戏有时会有不同的版本切换,不同的版本或许只是几个数值上的改变,游戏的机制和操作方式没有任何区别,这种不同版本的维护,对应的源代码其实都是一份,而通过条件编译,就可以实现利用一份源代码去维护两个或者多个版本

2.编译:

下一个过程就是编译,即是将代码经过编译器编译成汇编语言,为下一步汇编进一步翻译成二进制码做准备,我们的指令为:

gcc -S code.i -o code.s

文件如下:

在这里便是汇编代码的内容,为下一步做准备

3.汇编:

指令如下:

gcc -c code.s -o code.o

到这里,已经变成能由计算机看懂的二进制代码了。

4.链接:

所谓的链接,其实本质上讲是我们的.o文件完成后,同系统库链接,从而最终形成可执行程序的过程。

链接还是很好理解的,不过在这里我们谈到了库,由此便让我们先讲一讲何为库:

1.动态库,静态库概念和理解:

我们现在所写的代码,包括一些可以直接使用的函数,都是前人写好的,我们都是直接拿来用。

那么,这些函数存储他们的库在哪里呢?我们可以用下面的指令来查看:

ldd +可执行程序文件如下:

我们的ldd指令就可以查看我们所写的可执行程序都链接了哪些库,在这里你会看见三个,由此我们先引入库的分类:

1.库的分类:

库主要分为两种:

1.动态库:

.库类别为.so的即为动态库,这是在LINUX,在windows中为xxxx.dll

2.静态库:

库类别为.a的即为静态库,这是在LINUX,在windows中为xxx.lib

而在我们的LINUX系统中,是基本不会默认安装静态库的,而是动态库居多,至于这是为什么,我们先把这条结论记住,之后等我们分析优缺点的时候,不用我说,你自然就知道原因了。

2.库命名:

一个常见的库的命名如下:

一个库的真正名字实际上就是去掉lib,类别,版本号剩下的,在这里就是C即为它的名字。

3.动静态链接的理解:

我们应该怎样理解链接呢?我用这样一张图去体现链接的过程,图片如下:

我们前面知道,LINUX系统中默认的库都是动态库,所以如果你删除了动态库,LINUX系统相当于直接用不了了,要重新装。

根据上面的图,我们来明确一下动静态库,动静态链接的概念:

动态库:是C/C++或者其他第三方提供的所有方法的集合,被所有程序以链接的方式关联起来。
动态链接:库中的所有函数,都有其对应的入口地址,所谓的动态链接,其实就是把链接的库中的函数的入口地址拷贝到我们的可执行程序的特定位置,方便程序执行时可以到对应地址的位置调用函数.(注意,由于程序没有实际执行,也就是说,程序没有实际上在内存空间中占用位置,故在这里我们的入口地址都是逻辑地址而非物理上的地址)
静态库:是C/C++或者其他第三方提供的所有方法的集合,被所有程序以拷贝的方式,将需要的代码拷贝到自己的可执行程序中。
静态链接:以拷贝的形式,直接把所需要的函数代码拷贝到程序当中

在这里,我们就很清楚动静态链接的差别所在了:动态链接是拷贝地址,而静态链接是拷贝代码,就像我上面的图所示的,静态相当于直接把代码放到自己的身边,而动态的代码依旧在原来的位置

4.动静态链接的优缺点:

动态链接:
优点:形成的可执行程序体积比较小,比较节省资源
缺点:由于涉及到根据地址调用的过程,因此要慢一些,并且强依赖动态库,一旦动态库没了,所有依赖这个库的程序都没法执行了
静态链接:
优点:即使库被销毁,由于程序代码已经被拷贝到程序中,因此可以独立运行
缺点:体积太大,浪费资源

由此,让我们想想我之前问大家的那个问题?为什么LINUX支持的都是默认的动态文件,而不是静态的呢?

这正是因为静态库占用了过大的空间,浪费了更多的资源,我们可以拿这样一个例子来说明:

我们在gcc指令后面加上-static,就代表静态链接的意思,如下我们产生了这样的情况:

你会发现,861288是静态链接的可执行程序的大小,而8360是动态链接的,即使是一个很小的程序,就有这样的大小差距,现在我想,默认动态库不是没有道理的吧。

补充,有时我们-static没法执行:这个时候需要我们安装C的静态:

sudo yum install glibc-static

如果你需要C++的静态编译器,你可以安装:

sudo yum install -y libstdc+±static

5.引发思考,一款合格开发环境应该为我们做什么:

在这里我必须重新提一下VS,它确实称得上是一款十分优秀的开发平台,随着我自己配置编译器的过程,我逐渐意识到vs的强大之处,我们在VS可以直接调用库,可以调试,可以重新编译和卸载上一次的编译结果,可以查看汇编代码。

那在我看来,一个优秀的开发环境应该具备:

1.配置好了各种开发需要的库环境,例如lib 等

2.设置了合适的查找路径,方便调用

3.规定好我们形成的可执行程序的链接方式

总结:

本篇,我们进一步查看了程序是如何被翻译出来的,以及库的链接是如何进行的,同时我们也清楚的知道,我们所写的程序是多种库组合出来的结果,没有这些库,我们的函数就需要自己去构造,很麻烦,库进一步帮助我们提高了开发效率,同时,也让我们对开发环境的配置要求有了一个初步大概的认识。之后,我们将进一步配置完善我们的代码编写vim工具,并且可以在LINUX上实现我们的第一个有趣的程序。

目录
相关文章
|
21天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
60 4
|
21天前
|
Unix Linux Shell
linux入门!
本文档介绍了Linux系统入门的基础知识,包括操作系统概述、CentOS系统的安装与远程连接、文件操作、目录结构、用户和用户组管理、权限管理、Shell基础、输入输出、压缩打包、文件传输、软件安装、文件查找、进程管理、定时任务和服务管理等内容。重点讲解了常见的命令和操作技巧,帮助初学者快速掌握Linux系统的基本使用方法。
55 3
|
22天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
28天前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
69 3
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
251 1
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
40 1
|
1月前
|
弹性计算 网络协议 Ubuntu
如何在阿里云国际版Linux云服务器中自定义配置DNS
如何在阿里云国际版Linux云服务器中自定义配置DNS
|
1月前
|
JSON JavaScript 前端开发
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
87 0
|
1月前
|
存储 数据可视化 Linux
Linux 基础入门
Linux 基础入门
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
下一篇
无影云桌面