C语言编译过程分析及实验验证

一. 实验情况概述

本次实验完成了以下要求:

编写的一个简单C语言例子(至少包括3个C文件、至少一个头文件;含有初始化全局变量、未初始化全局变量、函数内静态变量与局部变量、赋值语句、if语句等),尽量不要包含第三方的头文件,按照预处理->编译->汇编->链接的操作过程,截图分析做成Word文档,理解并分析源程序与可执行程序Section的对应关系,特别是数据与指令,理解编译器的功能。

编写含有while循环、for循环的C程序,分析C语言的每个句子与汇编语言的对应关系,理解编译器的语义分析功能。

请编写含有函数调用(要有参数传递)的C程序,理解编译器在函数调用时所做的动作,并通过汇编试图去分析,验证函数的函数调用中的参数传递、调用与返回等

理解函数内局部变量如何分配,分析栈内分配过程。

编写的例子尝试编写一个makefile文件,然后进行make编译,然后通过make -j #n来编译,并验证加速编译。

二. 试验过程

一, 自己编写一个简单的C语言例子:

该例子包含四个文件,其中一个是头文件,具体如下:

图1.c语言源文件—main.c                            图2 function.c

图3. yjy.h                                                       图4 yjy.c

二, 分析编译过程:

1, 预处理:

使用如下命令进行预处理:

 

预处理后的文件内容如下:

图6 预处理后结果 main.i                                     图7 预处理后 yjy.i

图8  预处理后function.i

发现预处理的作用就是将头文件复制到.c文件中,并且将#define展开到文件使用的地方.

2.编译:

使用如下指令对文件进行编译:

 

图9  编译命令

编译后的文件内容为:

图10 编译后生成文件main.s                         图11function.s

 图12  yjy.s

由上图可知,编译的过程就是将经过宏展开的文件编译成汇编语言程序, 从而便于进一步汇编成为机器可以识别的机器语言程序.

3.汇编:

 

图13 汇编命令

汇编生成机器语言程序,是二进制程序,所以无法打开查看内容.

使用objdump 指令来查看sections节:

可以发现0.是代码段,1.是数据段,2.是bss未初始化段,3.是drectve,……

代码段大小为0xfc,数据段为0x04,

代码段对应main 中所有语句的内容,包含局部变量的使用,函数调用等,数据段包含静态变量,对应我们定义的全局变量.

下面的symbol table 是符号表,只要包含section符号,函数符号等.

4. 链接:

图14 链接命令

最后生成了可执行程序main.exe, 由于没有调用stdio.h,没有输入输出,所以程序执行完就退出了.

5. 分析可执行程序与源程序sections的对应关系:

使用如下指令查看可执行程序的sections:

使用如下指令查看可执行程序的sections:

结果如下:

 

可以看到包含很多节,其中代码段,是15,包含main 中的代码,数据段是24段,未初始化段bss是25段.

下面的符号表中定义了sections中的符号的含义,其他符号都定义了一段方法,可以在符号表中找到对应的内容.

三. 分析LLVM形式汇编程序

与高级语言程序 Section之间的对应关系:

 在这里仅拿main.ll 举例说明:

使用如下指令进行编译成LLVM格式的中间代码;

图15 生成LLVM命令

结果如下:

现对上图进行分析: 

开头四句话分别定义了:

模块ID,

源文件名,

目标数据层

目标编译类型

这些属于文件基本信息,不对应源文件位置.

 

 这里定义了两个全局变量,int32类型,初始值分别为1,0, 4位大小对齐方式.

 

接下来这里定义了main函数, 第11~20句分配了局部变量, 都是int32类型,四位对齐方式,

第21~24句存储了部分局部变量的初始值,

然后25~26句 加载了函数参数到内存,第27句调用了add函数, 参数为前两句加载的,通过值传递方式, 第28句存储运算结果,这些整体对应

第29~31句对应, 语义分别为:加载%2的值到%14, %15=%14+1;

将%15的值存储到%2的位置, 这样就完成了n++的过程.

接下来32~35句就是调用multiply()的过程, 分别是参数写, 调用函数, 返回值 ,和上述add()类似,在此不做详解.

第36~38句对应,和上述类似.

第39~42句分别为:

参数传递, 比较 %21, %22的大小 ,将结果存入%23, 然后是跳转指令, 根据%23的值决定跳转到label %24 或者 %26. 对应.

 之后是24和26两个标签,分别是加载%3到%25,或加载%4到%27然后无条件跳转到%28.

28标签中,有phi指令, phi节点是一个指令,用于根据当前块的前导选择一个值。也就是根据跳转的24或者26选择加载到%29的值到底是%25还是%27,这样就实现了maxn函数返回正确的值. 因为br指令判断后只会跳转一个标签,在这里选择返回哪个执行的值到%29实现正确的返回.然后再无条件跳转到30号标签.

此处我们已经开始了while的判断,30号标签正是判断内容的执行过程.

先加载%2到%31,然后比较判断%31 和 11 ,将结果存入%32,如果%31<11,则%32=0,然后吓一跳br根据%32的值选择跳转的位置.如果是0就跳转到33号标签,继续while内部的执行.

再然后就是33号标签对应while 语句内部的部分..

 

 

我们分析llvm的IR,发现最开始两条加载指令对应add函数的两个参数,然后call @add,然后存储返回结果,四条语句对应了一条函数执行的过程.

之后类似,又是四条语句对应了multiply的过程.之后三条语句对应了,这些语句都在前面解释过.

最后又无条件跳转到%30,继续判断是否n<11,然后再选择是否跳转到33继续执行while内的部分.

我们再看第42号lable:

 调用fibonacci,调用FrogJumpSteps,定义equal,存储初始值为0,这些类似的都在上面解释过.第77~81行对应

第82~85行,先加载两个局部变量值,然后判断是否相等,然后再根据结果跳转到48或49.

这两个标签就对应给equal赋值赋哪个.然后再无条件跳转到50

50好标签就是返回0.

后面还有一些函数声明,变量声明等.

至此,分析结束

 

四, 编写makefile文件:

在wsl中使用time make编译, 单进程和多进程多次交叉进行,得到如下

尝试使用Makefile编译,结果如下:

多进程平均时间< 单进程平均时间,故可以得出结论 ,多进程加快了编译.

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
附录c 编译程序实验 实验目的:用c语言对一个简单语言子集编制一个一遍扫描的编译程序,以加深对编译原理的理解,掌握编译程序的实现方法和技术。 语法分析 C2.1 实验目的 编制一个递归下降分析程序,实现对词法分析程序所提供的单词序列的语法检查和结构分析. C2.2 实验要求 利用C语言编制递归下降分析程序,并对简单语言进行语法分析. C2.2.1待分析的简单语言的语法 实验目的 通过上机实习,加深对语法制导翻译原理的理解,掌握将语法分析所识别的语法成分变换为中间代码的语义翻译方法. 实验要求 采用递归下降语法制导翻译法,对算术表达式、赋值语句进行语义分析并生成四元式序列。 实验的输入和输出 输入是语法分析提供的正确的单词串,输出为三地址指令形式的四元式序列。 例如:对于语句串 begin a:=2+3*4;x:=(a+b)/c end# 输出的三地址指令如下: (1) t1=3*4 (2) t2=2+t1 (3) a=t2 (4) t3=a+b (5) t4=t3/c (6) x=t4 算法思想 1设置语义过程 (1) emit(char *result,char *arg1,char *op,char *ag2) 该函数功能是生成一个三地址语句送到四元式表中。 四元式表的结构如下: struct {char result[8]; char ag1[8]; char op[8]; char ag2[8]; }quad[20]; (2)char *newtemp() 该函数回送一个新的临时变量名,临时变量名产生的顺序为T1,T2,…. Char *newtemp(void) { char *p; char m[8]; p=(char *)malloc(8); k++; itoa(k,m,10); strcpy(p+1,m); p[0]=’t’; return(p); } (2)主程序示意图如图c.10所示。 (2) 函数lrparser在原来语法分析的基础上插入相应的语义动作:将输入串翻译成四元式序列。在实验中我们只对表达式、赋值语句进行翻译。 语义分析程序的C语言程序框架 int lrparser() { int schain=0; kk=0; if(syn=1) { 读下一个单词符号; schain=yucu; /调用语句串分析函数进行分析/ if(syn=6) { 读下一个单词符号; if(syn=0 && (kk==0)) 输出(“success”); } else { if(kk!=1 ) 输出 ‘缺end’ 错误;kk=1;} else{输出’begin’错误;kk=1;} } return(schain); int yucu() { int schain=0; schain=statement();/调用语句分析函数进行分析/ while(syn=26) {读下一个单词符号; schain=statement(); /调用语句分析函数进行分析/ } return(schain); } int statement() { char tt[8],eplace[8]; int schain=0; {switch(syn) {case 10: strcpy(tt,token); scanner(); if(syn=18) {读下一个单词符号; strcpy(eplace,expression()); emit(tt,eplace,””,””); schain=0; } else {输出’缺少赋值号’的错误;kk=1; } return(schain); break; } } char *expression(void) {char *tp,*ep2,*eplace,*tt; tp=(char *)malloc(12);/分配空间/ ep2=(char *)malloc(12); eplace=(char *)malloc(12); tt =(char )malloc(12); strcpy(eplace,term ());/调用term分析产生表达式计算的第一项eplace/ while(syn=13 or 14) { 操作符 tt= ‘+’或者‘—’; 读下一个单词符号; strcpy(ep2,term());/调用term分析产生表达式计算的第二项ep2/ strcpy(tp,newtemp());/调用newtemp产生临时变量tp存储计算结果/ emit(tp,eplace,tt,ep2);/生成四元式送入四元式表/ strcpy(eplace,tp); } return(eplace); } char *term(void)/仿照函数expression编写/ char *factor(void) {char *fplace; fplace=(char *)malloc(12); strcpy(fplace, “ ”); if(syn=10) {strcpy(fplace,,token);/将标识符token的值赋给fplace/ 读下一个单词符号; } else if(syn=11) {itoa(sum,fplace,10); 读下一个单词符号; } else if (syn=27) {读下一个单词符号; fplace=expression();/调用expression分析返回表达式的值/ if(syn=28) 读下一个单词符号; else{输出‘}’错误;kk=1; } } else{输出‘(’错误;kk=1; } return(fplace); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值