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

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

前言

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


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

在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函数;也有可能是意外终止
相关文章
|
程序员 编译器 Linux
程序员进阶之路:程序环境和预处理(二)
程序员进阶之路:程序环境和预处理(二)
38 0
|
2月前
|
开发者
开发项目小问题总结,带有详解解释,让自己的代码走向完美之路,持续更新
这篇文章总结了开发项目中遇到的小问题及解决方案,包括字符串比较、资源管理、代码优化、异常处理等方面的内容,旨在帮助开发者写出更规范、高质量的代码。
38 2
开发项目小问题总结,带有详解解释,让自己的代码走向完美之路,持续更新
|
存储 自然语言处理 编译器
c语言学习第三十五课——程序环境与预处理
c语言学习第三十五课——程序环境与预处理
135 0
|
测试技术 Go
Go语言工程实践之测试 | 青训营笔记
Go语言工程实践之测试 | 青训营笔记
93 0
|
IDE 程序员 Go
如何借助工具快速生成代码?初学者容易踩的坑有哪些?
这篇内容继续分享重点内容:如何借助工具快速生成代码?初学者容易踩的坑有哪些?
179 0
如何借助工具快速生成代码?初学者容易踩的坑有哪些?