【C/C++】程序环境,探索程序的执行过程(习得无上内功《易筋经》的第一步)

阅读 87

2022-10-22

目录

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

在ANSIC(标准C,ANSI:美国国家标准总局)的任何一种实现中,存在两个不同的环境。

2.详解编译+链接

2.1翻译环境

在这里插入图片描述
在这里插入图片描述

  • 组成一个程序的每个源文件通过编译过程分别转换成目标文件。
  • 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
  • 链接器同时也会引入标准C函数库(链接库)中任何被该程序所用到的函数,而且它可以搜素程序员个人的程序集,将其需要的函数也链接到程序中。

2.2编译本身也分为几个阶段

在这里插入图片描述

gcc -E test.c -o test.i //预编译完成后就停下来,预处理之后产生的结果都放在test.i文件中
gcc -S test.c //编译完成后就停下来,结果保存在test.s中
gcc -c test.c  //汇编完成后就停下来,结果保存在test.o中。

在这里插入图片描述

代码:
test.c

#include<stdio.h>
extern int Add(int, int);
#define MAX 10

int main()
{
	//zhushi
	int max = MAX;
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("%d\n", c);

	return 0;
}

Add.c

int Add(int a1, int a2)
{
	return a1 + a2;
}

输出结果
在这里插入图片描述
可执行程序
在这里插入图片描述

  • 在项目文件夹的Debug下可以查看

预编译(预处理)

  1. 展开头文件
  2. 注释删除
  3. #define符号替换
  4. 经过预编译后源文件中的C语言代码不会改变。
  • 总的来说,预编译主要做一些文本相关的操作。

Linux命令

  • 使用指令:gcc -E test.c -o test.i
  • 预处理完成后就会停下,预处理的结果会放在test.i文件中。
    在这里插入图片描述
  • 如图说是,为test.i文件内容,它的头文件变为了800多行代码,删除了注释,#define符号被替换
    验证头文件展开
    在这里插入图片描述
  • 在include文件下,找到stdio.h文件,进行对比
    stdio.h文件内容:
    在这里插入图片描述
    我们可以在下面的图中看到,test.i文件的增加内容来自stdio.h文件。
    在这里插入图片描述

编译

  1. 把C语言代码转换成了汇编代码
  • 这里重点讲一下符号汇总

Linux命令

  • 使用指令:gcc -S test.c
  • 预处理完成后就停下来,结果保存在test.s中。

在这里插入图片描述

汇编

  1. 汇编后的文件是我们之前在翻译环境中提到的目标文件,目标文件的格式也是elf的

  2. 该文件把汇编指令转换成二进制指令

  3. 形成符号表

Linux命令

  • 使用指令:gcc -c test.c
  • 编译完成之后就停下来,结果保存在test.o文件中。
    在这里插入图片描述

详解符号表

  • 在Linux环境下,test.o文件为目标文件,它和可执行文件具有相同的格式:elf格式
  • 在test.o文件中我们可以查看查看它对应源文件的符号表,虽然它是二进制形式的,但我们可以通过readelf工具查看。

查看readelf工具Linux展示:

  • 使用指令:man readelf
    在这里插入图片描述
  • 这里我们使用-s选项查看符号表信息
    在这里插入图片描述
    我们可以进行如下操作查看test.o的符号表:
    在这里插入图片描述
  • test.o符号表中有main、Add、printf符号

查看Add.o符号表:
在这里插入图片描述

  • Add.o符号表中有Add符号

形成符号表

  • 我们已经知道,每个.o文件都有它对应的符号表,符号表里有编译过程中记录的符号,还有与符号相关联的地址。

  • 在编译过程中,test.i文件里面通过extern来声明Add函数,让编译器知道有这个符号。

  • 但Add函数是定义在其他文件里面的,我们只知道有这么个函数,而不能确定它在哪,所以它对应的地址是无效的(随机分配的地址
    在这里插入图片描述

  • 而main函数就在test.c文件中,我们可以找到它,所以我们会给它一个有效的地址。

  • printf函数是库函数,我们暂时不考虑。
    在这里插入图片描述

  • 在Add.c文件里实现了Add函数,我们可以找到它的地址,所以在编译过程,Add.i文件会对Add进行符号汇总,Add.o文件会对Add形成符号表。
    在这里插入图片描述

2.3.链接

  1. 合并段表
  2. 符号表的合并和重定位

合并段表

  • 每个可执行程序或test.o这样的目标文件,它们的格式都是elf文件的格式。这样的文件都有各自的段而且每个文件相同段的格式是相同的,合并段表就是把相同的段合并在一起。
    在这里插入图片描述

符号表的合并和重定位

  • 对所有符号进行合并,将相同的符号进行合并。
  • 取相同符号有意义的地址,进行合并。
  • 如果一个符号直邮无意义的符号,那就会报错。
  • 最终形成的符号表可以方便符号在调用时找到它的地址。
    在这里插入图片描述
    演示调用函数没有定义:
    在这里插入图片描述
  • 将Add函数注释后运行
    在这里插入图片描述
    链接错误,我们将Add函数注释后,编译器找不到Add的有效地址了,就会报错。

3.运行环境

程序执行的过程:

  1. 程序必须载入内存中。在有操作系统的环境中:一般载入内存的操作是由操作系统完成的。在独立的环境中(如单片机),程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始,接着便调用main函数。
  3. 开始执行程序。这个时候程序将使用一个运行时堆栈(static,函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留它们的值。
  4. 终止程序。正常终止main函数;也可能时意外终止。

总结:

精彩评论(0)

0 0 举报