C语言执行过程

2023-11-07

本文中涉及的代码地址:analyseExecutionOfC

文件结构:

analyse-execution-of-c
	|-- compilePreProcessSource.o
	|-- compilePreProcessSource.o.png
	|-- compilePreProcessSource.s
	|-- preProcessSource.c
	|-- source
	|-- source.c
	|-- source.png

引言

我们比较熟悉的C语言执行流程为:预处理、编译、汇编、链接、运行。但是各个阶段的具体流程又是什么呢?接下来针对每个阶段详细分析。

C语言的流行离不开gcc编译器的成功。gcc编译器帮助C程序完成四个阶段:预处理编译汇编链接。然后将链接后的程序交给OS 执行

本文简单介绍了C语言在执行之前的准备阶段,事实上每个阶段都是十分复杂的,绝对不是一篇或者几篇文章能够描述的,所以这里只能将描述停留在入门级上,希望能够对大家有所帮助。

GCC 编译过程

源文件

#include<stdio.h>
// 声明函数sum
int sum(int arg1, int arg2);
// main被gcc编译器的桩程序调用
int main(){
	sum(3, 5);
	return 0;
} 
// 初始化函数sum
int sum(int arg1, int arg2){
	int res = arg1 + arg2;
	return res;
}

预处理

在 linux 中运行:gcc -E source.c -o preProcessSource.c 。得到 preProcessSource.c 文件。

参数 -E 运行 preprocessor,-o 将运行结果输出到 preProcessSource.c 文件中。

预处理会丰富我们的源程序,调整删除多余的空格字符和制表符;将字符常数转化成对应的值;替换宏定义等。此时输出仍然是纯C代码。

编译

在 linux 中运行:gcc -S preProcessSource.c -o compilePreProcessSource.s。得到 compilePreProcessSource.s 文件。

参数 -SpreProcessSource.c 编译为汇编程序 compilePreProcessSource.s

编译可以被通俗地理解为将一种格式的字符串转化为另一种格式的字符串。将这个概念带入 -S 指令,可以认为 source.c 源码中出现的 sum(3, 5) 被转化为汇编语言 call sum

实际上C语言中函数的调用的确对应 X86汇编语言 的 call 指令。但是转化过程十分复杂。不同理论的语言有不同的编译原理,主要分为两各派系,一类是面向过程的编译,一类是面向对象的编译。

接下来我将站在逻辑层面(means I won’t code a real compiler, But I will guide you to understand the compilation process ),结合C语言编译器的实现过程描述C语言的实现过程。

  • 词法分析:分解源程序,得到一个个符号。

    将输入的源程序分解为一个个独立的词法符号,又记为token。

    假设下例中 a 的类型为 float

    词法分析
    输入:sum = a + 10
    输出:ID(sum) (=) ID(a) (+) (10)
  • 语法分析:分析程序结构,将词法符号串转化为语法分析树。

    韦氏词典:语法--组合单词以形成词组、从句或句子的方法

    语法分析
    输入:ID(sum) (=) ID(a) (+) (10)
    =
    ID(sum)
    (+)
    ID(a)
    (10)
  • 语义分析:确定语法分析得到的树形结构中每个节点符号的含义,建立变量和声明的关联,检查表达式的类型。
    此时需要引入一个新的概念:符号表。其作用是将表欧师傅映射到他们的类型和储存位置。

    符号表:

    table sum informationOfSum a informationOfA

    语法分析树经过语义分析形成语义分析树。

    语法分析树
    =
    ID(sum)
    (+)
    ID(a)
    (10)
    语义分析树
    =
    ID(sum)
    (+)
    ID(a)
    inttofloat
    (10)
  • 栈帧布局:按照机器要求的方式将变量、函数参数等分配到栈帧中。

    C语言栈帧布局如图所示:
    在这里插入图片描述
    可以参考这篇文章进一步了解C语言运行时栈结构:C程序方法调用

  • 翻译:生成中间代码,这是一种与任何特定语言无关的中间表示。

    为什么需要生成中间代码,而不是直接生成目标代码呢?技术上可定支持将语义分析树直接转化为目标代码,但是这样做不利于可移植性和模块化设计。

    一种好的中间代码有以下特点:

    • 能够充分利用语义分析阶段生成的语义分析树。
    • 对于希望支持的所有目标机器,它必须便于生成真实机器语言。
    • 中间表示的每种结构必须具有简单明了的含义,以便能够比较容易指定和重写中间表示的各种优化操作。

    这里我们使用三地址指令来描述中间代码。

    // 这就是中间表示的一种形式。站在Java程序的角度class字节码就是Java源程序的中间表示。
    t1 = inttofloat(10)
    t2 = id(a) + t1
    id(sum) = t2
    
  • 规范化:中间表示最终需要转化为机器语言或者汇编语言,我们需要仔细选择和定义规则以便中间表示能够和大多数机器的能力匹配。例如,我们定义规则–方法以外的标识符不能出现"()"。接下来我们就需要检查中间表示,如果发现不不符合规则的变量,将其修改为符合规则的变量。

    修改后的指令代码为:

    t1 = inttofloat(10)
    t2 = id<a> + t1
    id<sum> = t2
    
  • 指令选择

    中间表示的每个操作过程可能对应机器语言的多个操作指令,或者机器语言的一个操作指令对应中间表示的多个操作过程。此时我们就需要找到中间表示每个操作过程在机器语言中对应的最小指令集合。这个过程叫做指令选择。

    指令选择的目的是 找出一个给定的中间表示的恰当机器指令序列

    本文中我们假设规范后的程序就是恰当的机器指令序列

  • 控制流分析&数据流分析

    编译器将程序转换为含有大量临时变量的中间语言,转换后的程序必须在寄存器有限的计算机上运行。如果两个临时变量不会同时使用,则可以考虑将他们放在同一个寄存器中储存。因此,尽管有很多临时变量,我们通过调和之后只需要使用少量的寄存器保存他们。如果不能全部将他们放在寄存器中,超出的变量可以放在储存器中。

    因此编译器需要分析程序的中间表示,确定哪些临时变量会被同时使用,哪些变量会在将来被使用。

    为了对程序进行分析,通常有益的方法是生成程序的控制流程图。

    CSDN的flowchart中<>会被自动隐藏,所以接下来我们使用sum代表id<sum>,使用a代表id<a>。

    t1 = inttofloat(10)
    t2 = a + t1
    sum = t2

    从流程图上我们可以观察到同一时刻出现变量最多是3个,因此最多需要3各个寄存器储存变量的值。

    事实上之所以能够计算出来寄存器的使用情况,使用到的技术是控制流分析和数据流分析。

    控制流分析:分析指令的执行过程建立控制流程图,此图表示程序执行时所有可能流经的途径。

    数据流分析:收集程序变量的数据流信息。例如,活跃性分析计算每一个变量需被使用的其他地点。

  • 寄存器分配

    为程序中的每一个变量和临时数据选择一个寄存器,不在同一时刻活跃的变量可以共享同一个寄存器。

  • 代码流出

    用机器寄存器替代每一条机器指令中出现的临时变量名。

    source.c 文件到这里被处理为 compilePreProcessSource.s 文件。

汇编

在 linux 中运行:gcc -c compilePreProcessSource.s -o compilePreProcessSource.o 得到 compilePreProcessSource.o 文件。

参数 -c 将执行汇编过程,并通过 -o 将汇编结果输出到 compilePreProcessSource.o 文件中。

汇编结果展示

在这里插入图片描述
看到展示的结果,小伙伴们可能有一些懵,不用着急,随着接下来我们新概念的引入,相信能够解决你的疑惑。

区(section)(也称为段、节或部分)用于表示一个地址范围,操作系统以相同的方式对待和处理在该地址范围内的数据信息。区的概念主要用来表示编译器生成的目标文件(或可执行文件)中不同的信息区域。

链接器会将输入的目标文件内容按照一定规律组合成一个可执行程序。

这里仅仅讲解其中的部分区域,有兴趣的小伙伴可以参考gcc官方文档查看每一个区的作用。

text区、data区:这两个区用以保存程序。当程序运行时,text区通常不会改变,text区中的内容会被进程共享,其中含有指令代码和常数等内容。程序在执行时data区的内容通常是变化的,例如,C语言的变量就存放在data区中。

bss区:该区用于存放未初始化的变量或作为公共变量储存空间。

因为 .o 目标文件有自己规定的格式,每种格式有其特殊含义,所以建议有兴趣的小伙伴可以阅读gcc官方文档。只要理解不同的区被用来实现不同的功能,这些功能配合起来能够成功执行程序就行了。

链接

链接器会将输入的目标文件内容按照一定规律组合成一个可执行程序。

在 linux 中运行:gcc compilePreProcessSource.o -o source 得到 source 文件。

gcc 命令将 compilePreProcessSource.o 链接为可执行文件 source

链接结果展示

在这里插入图片描述
在汇编阶段已经介绍了链接器将目标文件转化为可执行文件。

可执行文件也是文件,有自己的格式,但是不同操作系统定义了不同的可执行文件的格式。根据可执行文件操作系统就能够按顺序执行对应的汇编指令,我们也能得到程序的运行结果。

问题

如果有什么问题可以在issue中发起提问。

另外稍后将会出一片文章,使用本文中的代码将程序的出栈入栈过程绘制出来。

参考文章

  • Preprocessing source files
  • 赵炯.linux0.11源码
  • [美]Andrew W.Appel, Maia Ginsburg.现代编译原理 C语言(修订版)[M].赵克佳,黄春,沈志宇,译.北京:人民邮电出版社,2018.4
  • [美] Alfred V.Aho, Monica S.Lam, Ravi Sethi, Jeffrey D.Ullman. 编译原理[M]. 赵建华,郑滔,戴新宇,译. 北京:机械工业出版社,2009.1
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言执行过程 的相关文章

随机推荐

  • Yii Framework 开发教程(28) Data Provider 简介

    这开始介绍Zii组件之前 先简要介绍一下Yii支持的数据源接口 IDataProvider IDataProvider主要功能是为UI组件如GridView ListView等提供数据源 同时也支持数据的分页和排序 下图为Yii内置的三种数
  • QQxml和json代码生成卡片的方法

    简介 最近看到qq群里总有人发一些奇怪的卡片 例如下面这个卡片 点击之后就会跳转到你自己的个人资料 是不是很神奇 其实这是依靠xml代码转成的卡片 通过一些软件对xml编译执行 可以编译xml的软件有很多 最常用的手机xml编译执行软件是华
  • Android Display架构分析

    Fence https www jianshu com p 3c61375cc15b android12 display分析 https www cnblogs com roger yu p 15641545 html hwcomper h
  • MIPS汇编语言实现选择排序算法

    MIPS汇编语言实现选择排序算法 1 流程图 2 C代码 3 MIPS代码 附注释 MIPS汇编语言实现选择排序算法 1 流程图 2 C代码 include
  • c++笔记(一)

    这里写的主要是一些c c 值得注意的地方和c primer笔记 方便以后回顾 复习c 当然会有一些错误 发现后再改正 当形参引用时 数组不能转化为指针 是连接符 当宏定义用多行时常用 1 c中不可以连续赋值 c 可以 如int a b c
  • 数据结构课程设计---------最少换车次数问题

    问题描述 设某城市有n个车站 并有m条公交线路连接这些车站 设这些公交车都是单向的 这n个车站被顺序编号为0 n 1 编号程序 输入该城市的公交线路数 车站个数 以及各公交线路上的各站编号 实现要求 求得从站0出发乘公交车至站n一1的最少换
  • ‘git‘不是内部或外部命令,也不是可运行的程序或批处理文件。

    一 出现问题 git 不是内部或外部命令 也不是可运行的程序或批处理文件 出现这个问题主要是git的环境变量没有设置 二 解决问题 首先右键我的电脑点击属性 在点击高级系统设置 点击环境变量 在下面这栏点击path设置环境变量 添加这三个环
  • mysql 获取倒数第二_如何从MySQL中的表中获取倒数第二条记录?

    要获得MySQL中最后一个记录 即倒数第二个 之前的记录 您需要使用子查询 语法如下SELECT FROM SELECT FROM yourTableName ORDER BY yourIdColumnName DESC LIMIT 2 a
  • python opencv 二值化 计算白色像素点

    贴部分代码 usr bin env python coding utf 8 import cv2 import numpy as np from PIL import Image area 0 def ostu img global are
  • Flutter audioplayers使用小结

    简介 audioplayers是一个可以支持同时播放多个音频文件的Flutter的库 用法也是相当的简单 AudioPlayer audioPlayer new AudioPlayer await audioPlayer play url
  • SqlServer的varchar最大长度

    SqlServer的varchar最大长度是8000 总会遇到这种字符串截断问题 但是在给表字段长度添加时最好还是不要添加为max 能用varchar n 的话就不必要去要求varchar max 性能问题 项目上确实出现过问题 这个博主的
  • 1115 裁判机

    1114 全素日 有一种数字游戏的规则如下 首先由裁判给定两个不同的正整数 然后参加游戏的几个人轮流给出正整数 要求给出的数字必须是前面已经出现的某两个正整数之差 且不能等于之前的任何一个数 游戏一直持续若干轮 中间有写重复或写错的人就出局
  • python数据分析:用户消费情况数据分析

    本次分析数据介绍 数据为某奶茶店2018年1月 2019年6月的销售数据 共计69 659项数据 用户共计23 570名 数据集共4个字段 user id 用户id order id 购买日期 order prodect 购买产品数 ord
  • 为什么java中类名要与文件名一致

    学习java程序过程中碰到了文件名与类名不一致问题 出现了报错 后面查了一下资料才知道为什么文件名与类名要一致 Java是被解释执行的 它在运行时并不是将所有的class文件全都放到内存中 而是在遇到import的时候才去相应的文件目录找相
  • 概率论与数理统计(3)--指数分布函数及其期望、方差

    1 什么是指数分布 设随机变量X具有如下形式的密度函数 那么则称X服从参数为 的指数分布 记为X EXP 指数分布的分布函数为 2 指数分布的期望和方差 数学期望 如果X 服从参数为 gt 0 的指数分布 那么指数分布X EXP 的数学期望
  • Conda 常用指令 (Mac)【下载 安装 环境配置 查看 创建 激活 配置cuda 拷贝环境】

    本文旨在介绍用conda配置一个新的深度学习环境的全过程 下载Anaconda 在 官网 中下载与python版本匹配的Anaconda Python与Anaconda版本匹配如下 图片源自 该博客 在本例中我下载的 Anaconda3 2
  • 12篇顶会论文,深度学习时间序列预测经典方案汇总

    早期的时间序列预测主要模型是诸如ARIMA这样的单序列线性模型 这种模型对每个序列分别进行拟合 在ARIMA的基础上 又提出了引入非线性 引入外部特征等的优化 然而 ARIMA类模型在处理大规模时间序列时效率较低 并且由于每个序列分别独立拟
  • aistudio提示找不到包,通过直接下载整个PaddleNLP的repo文件执行

    git clone https gitee com AI Mart PaddleNLP cd PaddleNLP python setup py install pip install regex nltk beautifulsoup4 当
  • mysql 同步失败_线上MYSQL同步报错故障处理方法总结

    前言 在发生故障切换后 经常遇到的问题就是同步报错 下面是最近收集的报错信息 记录删除失败 在master上删除一条记录 而slave上找不到 Last SQL Error Could not execute Delete rows eve
  • C语言执行过程

    系列1 C语言执行过程 系列2 C程序方法调用 系列3 CS IP 寄存器 本文中涉及的代码地址 analyseExecutionOfC 文件结构 analyse execution of c compilePreProcessSource