【C进阶】程序的环境

本文详细介绍了C程序从源代码到可执行文件的编译和链接过程。在翻译环境中,源代码经历预处理、编译和汇编三个步骤,生成目标文件。预处理包括头文件包含、宏替换和注释删除;编译涉及语法、词法和语义分析;汇编则将编译后的代码转换为机器指令。链接阶段,链接器合并目标文件和库,完成符号表合并和重定位,最终生成可执行程序。在运行环境中,程序加载到内存,开始执行并使用运行时堆栈。
摘要由CSDN通过智能技术生成

目录

1、引言

2、翻译环境

   (一)编译

           (1)预处理

           (2)编译环节

           (3)汇编

    (二)链接

3、运行环境


1、引言

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

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

比如说我们写了一个源文件test.c,其经过一个详细的处理后会变成test.exe文件,此篇文章会详细讲解test.c文件是如何到test.exe文件的,其实主要有两个大的过程,1是编译,2是链接。而此过程所依赖的主要环境就是翻译环境,而test.exe文件想运行起来所依赖的环境是运行环境。

  • 正文开始:

2、翻译环境

  • 先看一幅图:

假设我们在一个工程里面写了test.c,contact.c,common.c这三个源文件。每一个.c文件都会经过编译器处理,最后各自生成一个目标文件(windows底下以.obj作为后缀),这些目标文件和链接库一起经过链接器最后生成可执行程序。

  1. 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
  2. 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
  3. 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
  • 解释第3条:(用printf举例)

 Libraries就是库,printf就是包含在以后缀.LIB命名的静态库里头。要使用pritnf,就应该把这些.LIB文件的链接库链接进来

  • 而编译又可分为以下三个部分(用一张图先过渡下)

下面将会详细介绍一个源文件是如何到可执行程序的。

(一)、编译

  • 先看一段代码:
#include<stdio.h>
int g_val = 2021;
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}

在VS环境中,我们执行该程序需要按ctrl+F5,但是此时会直接生成可执行程序,无法看到具体转换过程,所以采用linux可以很好的观察到这一现象:即编译拆分成预处理+编译+汇编

  • 注意:

在linux环境中,如果直接使用gcc来编译test.c文件,它默认生成一个a.out的一个文件,该文件同样也是个可执行程序,为了避免和VS环境一样直接生成可执行程序而无法观察到细节,因此我们需要在预处理后停下来,编译后停下来,汇编后停下来。操作如下:

1. 预处理 选项 gcc -E test.c -o test.i

预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。

2. 编译 选项 gcc -S test.c

编译完成之后就停下来,结果保存在test.s中。

3. 汇编 gcc -c test.c

汇编完成之后就停下来,结果保存在test.o中。

  • 具体过程见下文:

(1)预处理

在linux环境中使用gcc test.c -E这个选项来编译test.c文件,就可以使其预处理后停下来

而这个命令执行后,会出现一堆代码,此时我们再使用gcc -test.c -E > test.i这个操作就可以把出现的一堆代码重定向输出到test.i文件里头,打开test.i文件

先看三幅图:

  • 图一:

  •  图二:

此时我们在test.c文件里头添加些宏定义,再和预处理阶段对比看看:

  •  图三:

当我们在test.c文件中加上一些注释时,在预处理阶段又会发生什么呢?

  • 通过上述三张图:可以总结出在预处理阶段的3条结论:
  1. 完成了头文件的包含
  2. #define定义的符号和宏的替换
  3. 注释删除
  • 而上述三个操作都属于文本操作。

(2)编译环节

在经过上述的预处理后,就来到了编译环节:

我们使用gcc test.i -S这个指令进行编译,最终生成test.s文件,我们打开test.s文件来看看:

先简单截取一段:

 上述代码即汇编代码,综上可得出结论:

  • 汇编过程即把C语言代码转化成汇编代码,主要做了以下4个内容:
  1. 语法分析
  2. 词法分析
  3. 语义分析
  4. 符号汇总
  •  先简单解释下符号汇总:

把全局变量和函数名汇总起来,比如main和全局变量g_val,符号汇总的用处是在下一阶段汇编和链接中体现出来的,下文会详细介绍:

  • 简单举一例:

(3)汇编

在我们编译结束后生成了test.s文件,接下来进入汇编

我们使用gcc test.s -c命令进行汇编,最后生成test.o这样的一个文件,而这个test.o文件就类似于windows上的test.obj文件,即生成了目标文件。我们打开test.o文件:(简单截取一段)

上图中放的其实都是二进制信息。由此得到汇编的结论:

  1. 把汇编代码转换成机器指令(二进制指令) 
  2. 形成符号表

而汇编中的生成符号表和编译中的符号汇总又有什么关联呢?接下来详细介绍:

  • 重新举个例子:

如图所示,我们创建了add.c和test.c两个源文件

 在linux环境中,test.c文件和add.c文件会经过预处理、编译、汇编生成对应的test.o文件和add.o文件。

而汇编中的形成符号表其实就是有一张表格,里面既有汇总的符号,也有这些符号对应的地址

  • 拿上例说明:

  • 注:

test.c汇总的Add符号由于Add是声明的,找不到确切的地址,所以用红色记号笔简要表示。

 理解了符号表,当生成.o文件(目标文件)时,多个.o文件经过连接器链接会生成可执行程序即test.exe文件。这个过程链接就起到了主要作用,详解见下文:

(二)、链接

  • 链接主要实现的操作有两个:
  1. 合并段表
  2. 符号表的合并和符号表的重定位

如上图test.c文件和add.c文件会经过预处理、编译、汇编生成对应的test.o文件和add.o文件。而它俩要链接在一起,首先就要发生合并段表。

  • 合并段表:

像add.o这样的文件是有它字节的格式的,它会把自己分成几个段。自然test.o也会这么分,两个段的格式是一样的,只不过内容是不一样的。而合并段表就是把对应的段上的数据合并在一起,如图:

  • 注:这些段表的格式都是elf文件格式 
  • 符号表的合并和符号表的重定位:

 在上述两个操作执行完毕后,就可生成可执行程序了,此时整个翻译环境就讲解完毕。

3、运行环境

  • 程序执行的过程:
  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。
  • 66
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 57
    评论
评论 57
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三分苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值