程序员进阶之路:程序环境和预处理(一)

简介: 程序员进阶之路:程序环境和预处理(一)

前言

程序员的日常工作离不开程序环境和预处理,本文将为您详细解析它们的内部机制和运作原理。


程序的翻译环境和执行环境

在ANSI C(标准C)的任何一种实现中,存在两个不同的环境。

  • 翻译环境,在这个环境中源代码被转换为可执行的机器指令
  • 执行环境,它用于实际执行代码

什么意思呢?

计算机只能执行二进制指令,我们写的C语言程序属于文本信息,计算机不能直接理解

翻译环境:在这个环境中就是将C语言代码翻译成二进制指令,这些指令会放在可执行程序当作。

执行环境:当我们得到可执行程序时,如何让它运行起来呢?这时就需要到执行环境,执行环境就是用来执行二进制的代码。

翻译环境的整体流程如下:

我们写的每一个.c文件都属于源文件。一个项目当中可以包含多个.c文件。在翻译环境中,每个源文件会单独的经过编译器处理,生成目标文件(.obj文件),每个目标文件与链接器捆绑在一起,形成一个单一而完整的可执行程序。

那链接器的作用是什么呢?

链接器会引入标准C函数库中被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。

翻译环境

在翻译环境下,计算机会进行两大操作,一个是编译,一个是链接。

编译又可分为3个阶段:

在开始编译后,会先进入预编译阶段,预编译阶段会做什么呢?这些我们在VS这种集成开发环境中是看不到的。有使用vscode的小伙伴可以试一试。

例如我们写一个简单的程序并进行预处理,如下图:

 

输入指令让程序先进行预处理,预处理后我们可以看到在我们写的代码前多了800多行代码,这800多行代码是怎么来的呢?其实这增加的代码是我们包含的头文件s t d i o . h里的内容。

那在预处理阶段,计算机都对程序做了什么呢?主要做了一下三个操作:

  • 注释的删除
  • #include 头文件的包含
  • #define 符号的替换

这些都属于文本操作,所有的预处理指令都是在预处理阶段处理的。

如下图,左边为翻译后的汇编指令。

 

预处理之后,程序就会进入编译阶段,编译阶段会将我们写的C语言代码翻译成汇编指令,它主要会对C程序进行一下操作:

  • 语法分析
  • 词法分析
  • 语义分析
  • 符号汇总

编译之后程序进入汇编阶段,汇编阶段就会生成目标文件。目标文件中存放的都是二进制指令。

 

所以在汇编阶段,计算机主要干了一件事:把汇编代码,翻译成机器可以读懂的二进制指令。

目标文件一般是不打开的,如果要强制打开,看到的也只是一些乱码。

在汇编阶段其实有一个动作叫:形成符号表

在编译阶段程序会进行符号汇总,汇编阶段又会进行形成符号表的操作。这有什么用呢?

其实它们最终的用途是在链接阶段。在链接阶段,就会去查看这个符号表

在链接阶段会进行一下两个操作:

  • 合并段表
  • 符号表的合并和符号表的重定位

这两个操作主要干什么呢?

我们可以先写一个简单的程序来观察:

 

在我们生成目标文件时,我们可以打开生成的这个二进制文件,打开文件之后,虽然说大部分东西都看懂,但是我们可以找到一个 E L F

这种二进制文件看似乱码,实则具有自己的组织格式。在Linux环境下都是使用ELF这种组织格式来存储的。我们以Linux环境下为例。

核心就是,像elf这种组织格式,它的存储方式其实是将数据划分为一个一个的段,按照不同的段来存储(数据段,文本段,只读数据段等)。

也可以输入指令去读这个二进制文件中的符号,打开我们可以看到一个个的全局符号

在编译阶段,就已经进行了这些全局符号的汇总。

我们分两个文件去写程序:

test.c

1. extern int Add(int, int);
2. int main()
3. {
4.  int a = 10;
5.  int b = 20;
6.  int c = Add(a, b);
7.  printf("%d", c);
8. }

add.c

1. int Add(int x, int y)
2. {
3.  return x + y;
4. }

在编译时就会对两个文件中的符号进行汇总,add.c里边汇总一个符号Add,test.c文件汇总两个符号Add和main。

编译后进入汇编阶段,汇编阶段形成符号表,符号表里边存放的是汇总的符号,以及相应的地址。

这里回到链接阶段的两个操作:

合并段表,我们前边知道在汇编阶段,生成的二进制文件,数据存储是分段存储的。上述我们使用两个文件来写程序,两个文件都是这样分段存储的。

 

到了链接阶段,两个文件合并对应位置上的数据段,并生产.exe结尾的可执行程序,它也是符合elf这样的分段存储。

还有符号表的合并,在add.c中汇总一个符号Add,有函数定义为有效地址,test.c中Add仅仅是一个声明,所以并不是一个有效地址,两个文件的符号表也会在链接阶段进行合并,生成一个新的符号表

这有什么用呢?在程序运行时,就会通过符号表上的地址去找到对应的函数。

这整个过程就是程序在编译、链接生成可执行程序的整个过程。

运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止
相关文章
|
6月前
|
资源调度 前端开发 算法
鸿蒙OS架构设计探秘:从分层设计到多端部署
本文深入探讨了鸿蒙OS的架构设计,从独特的“1+8+N”分层架构到模块化设计,再到智慧分发和多端部署能力。分层架构让系统更灵活,模块化设计通过Ability机制实现跨设备一致性,智慧分发优化资源调度,多端部署提升开发效率。作者结合实际代码示例,分享了开发中的实践经验,并指出生态建设是未来的关键挑战。作为国产操作系统的代表,鸿蒙的发展值得每一位开发者关注与支持。
|
3月前
|
JavaScript 前端开发 UED
【HarmonyOS Next之旅】基于ArkTS开发(二) -> UI开发四
本文介绍了Web组件开发与性能优化的相关内容。在Web组件开发部分,涵盖创建组件、设置样式与属性、添加事件和方法以及场景示例,如动态播放视频。性能提升方面,推荐使用数据懒加载、条件渲染替代显隐控制、Column/Row替代Flex、设置List组件宽高及调整cachedCount减少滑动白块等方法,以优化应用性能与用户体验。
175 56
|
Web App开发 前端开发 测试技术
Xpath Helper 在新版Edge中的安装及解决快捷键冲突问题
Xpath Helper 在新版Edge中的安装及解决快捷键冲突问题
772 0
|
6月前
|
SQL Unix API
夏令时的坑:你的数据库真的能正确处理时间跳变吗?
时区是地球上使用相同标准时间的区域。由于地球的自转,为了保证各地的时间与当地的日出日落相协调,全球划分为多个时区。
250 0
|
11月前
|
存储 网络协议 Linux
AWS实操-EC2-创建购买linux(centos)EC2服务器
AWS实操-EC2-创建购买linux(centos)EC2服务器
|
前端开发 JavaScript
前端必修之一 彻底理解原型和原型链
【8月更文挑战第2天】理解原型和原型链
192 11
|
存储 缓存 前端开发
(三)Nginx一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...想要的这都有!
早期的业务都是基于单体节点部署,由于前期访问流量不大,因此单体结构也可满足需求,但随着业务增长,流量也越来越大,那么最终单台服务器受到的访问压力也会逐步增高。时间一长,单台服务器性能无法跟上业务增长,就会造成线上频繁宕机的现象发生,最终导致系统瘫痪无法继续处理用户的请求。
400 1
|
存储 前端开发 JavaScript
HTML五彩缤纷的爱心
HTML五彩缤纷的爱心
274 1
|
JavaScript 前端开发 数据格式
URL编码【详解】——Javascript对URL进行编码解码的三种方式的区别和使用场景,axios请求拦截器中对get请求的参数全部进行URL编码
URL编码【详解】——Javascript对URL进行编码解码的三种方式的区别和使用场景,axios请求拦截器中对get请求的参数全部进行URL编码
765 0
|
存储 JSON Java
mapstruct最佳实践
当两个对象属性不一致时,比如User对象中某个字段不存在与UserVo当中时,在编译时会有警告提示,可以在@Mapping中配置 ignore = true,当字段较多时,可以直接在@Mapper中设置unmappedTargetPolicy属性或者unmappedSourcePolicy属性为 ReportingPolicy.IGNORE即可。 如果项目中也同时使用到了 Lombok,一定要注意 Lombok的版本要等于或者高于1.18.10,否则会有
260 0