为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

编译原理及实践教程第1章参考答案

2018-04-08 39页 doc 107KB 107阅读

用户头像

is_841159

暂无简介

举报
编译原理及实践教程第1章参考答案编译原理及实践教程第1章参考答案 1. 翻译程序:能够将某种语言写的程序转换成另一种语言的程序,而且后者与前者在逻辑上是等价的。 编译程序:是一种将高级语言程序(源程序)翻译成低级语言(目标程序)的程序 解释程序:接受某高级语言的一个语句输入,进行解释并控制计算机执行,马上得到 这句的执行结果,然后再接受下一句。 源程序:被翻译的程序。 目标程序:翻译后的程序。 遍:对源程序或源程序的中间结果从头到尾扫描一次,并作有关的加工处理,生成新的中间结果或目标程序。 编译前端:主要指与源语言有关,与目标语言无关的部分,通...
编译原理及实践教程第1章参考答案
编译原理及实践教程第1章参考答案 1. 翻译程序:能够将某种语言写的程序转换成另一种语言的程序,而且后者与前者在逻辑上是等价的。 编译程序:是一种将高级语言程序(源程序)翻译成低级语言(目标程序)的程序 解释程序:接受某高级语言的一个语句输入,进行解释并控制计算机执行,马上得到 这句的执行结果,然后再接受下一句。 源程序:被翻译的程序。 目标程序:翻译后的程序。 遍:对源程序或源程序的中间结果从头到尾扫描一次,并作有关的加工处理,生成新的中间结果或目标程序。 编译前端:主要指与源语言有关,与目标语言无关的部分,通常包括词法分析、语法分析、语义分析和中间代码生成,与机器无关部分的代码优化 编译后端:指与目标机器有关的部分。如与机器有关的优化、目标代码生成 2.高级语言程序有哪两种执行方式,其特点是什么,阐述其主要异同点。 答:高级语言程序有编译程序和解释程序两种执行方式; 编译程序(Compiler)——将高级程序设计语言程序翻译成逻辑上等价的低级语言(汇 编语言,机器语言)程序的翻译程序。 解释程序(Interpreter)——将高级程序设计语言写的源程序作为输入,边解释边执行 源程序本身,而不产生目标程序的翻译程序。 3.编译过程可分为哪些阶段,各个阶段的主要任务是什么, 答:编译过程逻辑上可分为五个阶段:词法分析、语法分析、语义分析与中间代码生成、代码优化、目标代码生成。 第一阶段:词法分析任务: 从左到右扫描源程序,识别出每个单词 第二阶段:语法分析任务: 在词法分析的基础上,根据语言的语法规则,将单词符号串分解成各类语法短语(例:程序、语句、表达式)。 第三阶段:语义分析和中间代码生成任务:对语法分析所识别出的各类语法范畴分析其含义,进行初步的翻译(翻译成中间代码)。 第四阶段: 代码优化任务:对已产生的中间代码进行加工变换,使生成的目标代码更为高效(时间和空间)。 第五阶段:目标代码的生成任务:把中间代码(或经优化的中间代码)变换成特定机器上的低级语言代码。 4. 编译程序有哪些主要构成成分,各自的主要功能是什么, 答:(1) 记号(token) 当扫描程序将字符收集到一个记号中时,它通常是以符号表示这个记号;这也就是说,作为一个枚举数据类型的值来表示源程序的记号集。 (2) 语法树(syntax tree) 如果分析程序确实生成了语法树,它的构造通常为基于指针的标准结构,在进行分析时动态分配该结构,则整棵树可作为一个指向根节点的单个变量保存。结构中的每一个节点都是一个记录,它的域表示由分析程序和之后的语义分析程序收集的信息。 (3) 符号表(symbol table) 这个数据结构中的信息与标识符有关:函数、变量、常量以及数据类型。符号表几乎与编译器的所有阶段交互:扫描程序、分析程序或将标识符输入到表格中的语义分析程序; 语义分析程序将增加数据类型和其他信息;优化阶段和代码生成阶段也将利用由符号表提供的信息选出恰当的代码。因为对符号表的访问如此频繁,所以插入、删除和访问操作都必须比常规操作更有效。尽管可以使用各种树的结构,但杂凑表却是达到这一要求的标准数据结构。有时在一个列表或栈中可使用若干个表格。 (4) 常数表(literal table) 常数表的功能是存放在程序中用到的常量和字符串,因此快速插入和查找在常数表中也十分重要。但是,在其中却无需删除,这是因为它的数据全程应用于程序而且常量或字符串在该表中只出现一次。 (5) 中间代码(intermediate code) 根据中间代码的类型(例如三元式代码)和优化的类型,该代码可以是文本串的数组、临时文本文件或是结构的连接列表。对于进行复杂优化的编译器,应特别注意选择允许简单重组的表示。 (6) 临时文件(t e m p o r a ry file) 计算机过去一直未能在编译器时将整个程序保留在存储器中。这一问题已经通过使用临时文件来保存翻译时中间步骤的结果或通过―匆忙地‖编译(也就是只保留源程序早期部分的足够信息用以处理翻译)解决了。 5. 编译程序的构造需要掌握哪些原理和技术, 答:源语言:理解其结构和含义 目标语言:必须清楚硬件的系统结构和指令的格式等 编译方法 6. 编译程序构造工具的作用是什么, 答:自动生成:LEX(词法分析)与YACC(用于自动产生LALR分析表) 7. 编译技术可应用在哪些领域, 答:语言的结构化编辑器 :Turbo-Edit、editplus和Ultraedit等 语言程序的调试工具 语言程序的测试工具 高级语言之间的转换工具 交叉编译程序 思考题1 • 1. 什么是编译程序 , • 一种将高级语言程序(源程序)翻译成低级语言(目标程序)的程序 • 2. 编译过程分哪些阶段,各阶段的功能和任务是什么? • 略 • 3. 写出C语言中字符集、单词、数据类型、各种表达式、语句和程序的组成 答:C语言字符集由字母,数字,空格,标点和特殊字符组成。 C语言常用单词:1. 数据类型关键字(8个): ...int , short ,long, signed, unsigned ...char , float, double 2. 程序控制关键字(10个): ..1) 分支结构: ....if , else, switch, case, default, break ..2) 循环结构: ....do , while, for, continue 3. 函数及数据存储关键字(6个): ...void , return, auto, register, static, extern 4. 构造数据类型关键字(5个): ...struct, union, enum, typedef, sizeof 5. 其它3个不常用(3个) ...goto, const, volatile C语言数据类型包括: 1.基本类型 (1)整型 a.基本型b.长整型c.短整型d.无符号型 (2)实型 a.单精度型b.双精度型 (3)字符型 (4)枚举类型 2.构造类型 (1)数组类型(2)结构体类型(3)共用体类型 3.指针类型 4.空类型 C语言的运算符可分为以下几类: 1.算术运算符 用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共七种。 2.关系运算符 用于比较运算。包括大于(>)、小于(<)、等于(==)、 大于等于(>=)、小于等于(<=)和不等于(!=)六种。 3.逻辑运算符 用于逻辑运算。包括与(&&)、或(||)、非(!)三种。 4.位操作运算符 参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。 5.赋值运算符 用于赋值运算,分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共十一种。 6.条件运算符 这是一个三目运算符,用于条件求值(?:)。 7.逗号运算符 用于把若干表达式组合成一个表达式(,)。 8.指针运算符 用于取内容(*)和取地址(&)二种运算。 9.求字节数运算符 用于计算数据类型所占的字节数(sizeof)。 10.特殊运算符 有括号(),下标[],成员(?,.)等几种。 优先级和结合性 C语言中,运算符的运算优先级共分为15级。1级最高,15级最低。在表达式中,优先级较高的先于优先级较低的进行运算。 而在一个运算量两侧的运算符优先级相同时, 则按运算符的结合性所规定的结合方向处理。 C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。例如算术运算符的结合性是自左至右,即先左后右。如有表达式x-y+z则y应先与―-‖号结合, 执行x-y运算,然后再执行+z的运算。这种自左至右的结合方向就称为―左结合性‖。而自右至左的结合方向称为―右结合性‖。 最典型的右结合性运算符是赋值运算符。如x=y=z,由于―=‖的右结合性,应先执行y=z再执行x=(y=z)运算。 C语言运算符中有不少为右结合性,应注意区别,以避免理解错误。 算术运算符和算术表达式基本的算术运算符 1.加法运算符―+‖加法运算符为双目运算符,即应有两个量参与加法运算。如a+b,4+8等。具有右结合性。 2.减法运算符―-‖减法运算符为双目运算符。但―-‖也可作负值运算符,此时为单目运算,如-x,-5等具有左结合性。 3.乘法运算符―*‖双目运算,具有左结合性。 4.除法运算符―/‖双目运算具有左结合性。参与运算量均为整型时, 结果也为整型,舍去小数。如果运算量中有一个是实型,则结果为双精度实型。 void main(){ printf("\n\n%d,%d\n",20/7,-20/7); printf("%f,%f\n",20.0/7,-20.0/7); } 双目运算具有左结合性。参与运算量均为整型时, 结果也为整型,舍去小数。如果运算量中有一个是实型,则结果为双精度实型。 printf("\n\n%d,%d\n",20/7,-20/7); printf("%f,%f\n",20.0/7,-20.0/7); 本例中,20/7,-20/7的结果均为整型,小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算,因此结果也为实型。 5.求余运算符(模运算符)―%‖双目运算,具有左结合性。要求参与运算的量均为整型。 求余运算的结果等于两数相除后的余数。 void main(){ printf("%d\n",100%3); } 双目运算,具有左结合性。求余运算符% 要求参与运算的量均为整型。本例输出100除以3所得的余数1。 自增1,自减1运算符 自增1运算符记为―++‖,其功能是使变量的值自增1。自减1运算符记为―--‖,其功能是使变量值自减1。自增1,自减1运算符均为单目运算,都具有右结合性。可有以下几种形式: ++i i自增1后再参与其它运算。--i i自减1后再参与其它运算。 i++ i参与运算后,i的值再自增1。 i-- i参与运算后,i的值再自减1。 在理解和使用上容易出错的是i++和i--。 特别是当它们出在较复杂的表达式或语句中时,常常难于弄清,因此应仔细分析。 void main(){ int i=8; printf("%d\n",++i); printf("%d\n",--i); printf("%d\n",i++); printf("%d\n",i--); printf("%d\n",-i++); printf("%d\n",-i--); } i<--8 i<--i+1 i<--i-1 i<--i+1 i<--i-1 i<--i+1 i<--i-1 int i=8; printf("%d\n",++i); printf("%d\n",--i); printf("%d\n",i++); printf("%d\n",i--); printf("%d\n",-i++); printf("%d\n",-i--); i的初值为8 第2行i加1后输出故为9; 第3行减1后输出故为8; 第4行输出i为8之后再加1(为9); 第5行输出i为9之后再减1(为8) ; 第6行输出-8之后再加1(为9); 第7行输出-9之后再减1(为8) void main(){ int i=5,j=5,p,q; p=(i++)+(i++)+(i++); q=(++j)+(++j)+(++j); printf("%d,%d,%d,%d",p,q,i,j); } i<--5,j<--5,p<--0,q<--0 i+i+i--->p,i+1-->i,i+1-->i,i+1-->i j+1->j,j+1->j,j+1->j,j+j+j->q int i=5,j=5,p,q; p=(i++)+(i++)+(i++); q=(++j)+(++j)+(++j); 这个程序中,对P=(i++)+(i++)+(i++)应理解为三个i相加,故P值为15。然后i再自增 1三次相当于加3故i的最后值为8。而对于q 的值则不然,q=(++j)+(++j)+(++j)应理解为q 先自增1,再参与运算,由于q自增1三次后值为8,三个8相加的和为24,j的最后值仍 为8。算术表达式表达式是由常量、变量、函数和运算符组合起来的式子。 一个表达式有 一个值及其类型, 它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。 单个的常量、变量、函数可以看作是表达式的特例。 算术表达式 是由算术运算符和括号连接起来的式子, 以下是算术表达式的例子: a+b (a*2),c (x+r)*8-(a+b),7 ++i sin(x)+sin(y) (++i)-(j++)+(k--) 赋值运算符和赋值表达式 简单赋值运算符和表达式,简单赋值运算符记为―=‖。由―= ‖连接的式子称为赋值表达式。其一般形式为: 变量=表达式 例如: x=a+b w=sin(a)+sin(b) y=i+++--j 赋值表达式的功能是计算表达式的值再赋予左边的变量。赋值运算符具有右结合性。因此 a=b=c=5 可理解为 a=(b=(c=5)) 在其它高级语言中,赋值构成了一个语句,称为赋值语句。 而在C中,把―=‖定义为运算符,从而组成赋值表达式。 凡是表达式可以出现的地方均可出现赋值表达式。例如,式子x=(a=5)+(b=8)是合法的。它的意义是把5赋予a,8赋予b,再把a,b相加,和赋予x ,故x应等于13。 在C语言中也可以组成赋值语句,按照C语言规定, 任何表达式在其未尾加上分号就构成为语句。因此如x=8;a=b=c=5;都是赋值语句,在前面各例中我们已大量使用过了。 如果赋值运算符两边的数据类型不相同, 系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规定如下: 1.实型赋予整型,舍去小数部分。前面的例2.9已经说明了这种情况。 2.整型赋予实型,数值不变,但将以浮点形式存放, 即增加小数部分(小数部分的值为0)。 3.字符型赋予整型,由于字符型为一个字节, 而整型为二个字节,故将字符的ASCII码值放到整型量的低八位中,高八位为0。 4.整型赋予字符型,只把低八位赋予字符量。 void main(){ int a,b=322; float x,y=8.88; char c1='k',c2; a=y; x=b; a=c1; c2=b; printf("%d,%f,%d,%c",a,x,a,c2); } int a,b=322; float x,y=8.88; char c1='k',c2; printf("%d,%f,%d,%c",a=y,x=b,a=c1,c2=b); 本例表明了上述赋值运算中类型转换的规则。a为整型,赋予实型量y值后只取整数8。x为实型,赋予整型量b值322, 后增加了小数部分。字符型量c1赋予a变为整型,整型量b赋予c2 后取其低八位成为字符型(b的低八位为01000010,即十进制66,按ASCII码对应于字符B)。 复合赋值符及表达式 在赋值符―=‖之前加上其它二目运算符可构成复合赋值符。如 +=,-=,*=,,=,%=,<<=,>>=,&=,^=,|=。 构成复合赋值表达式的一般形式为: 变量 双目运算符=表达式 它等效于 变量=变量 运算符 表达式 例如: a+=5 等价于a=a+5 x*=y+7 等价于x=x*(y+7) r%=p 等价于r=r%p 复合赋值符这种写法,对初学者可能不习惯, 但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。逗号运算符和逗号表达式在 逗号运算符 C语言中逗号―,‖也是一种运算符,称为逗号运算符。 其功能是把两个表达式连接起来组成一个表达式, 称为逗号表达式。 其一般形式为: 表达式1,表达式2 其求值过程是分别求两个表达式的值,并以表达式2的值作为整个逗号表达式的值。 void main(){ int a=2,b=4,c=6,x,y; y=(x=a+b),(b+c); printf("y=%d,x=%d",y,x); } a<--2,b<--4,c<--6,x<--0,y<--0 x<--a+b,y<---b+c 本例中,y等于整个逗号表达式的值,也就是表达式2的值,x是第一个表达式的值。对于逗号表达式还要说明两点: 1.逗号表达式一般形式中的表达式1和表达式2 也可以又是逗号表达式。例如: 表达式1,(表达式2,表达式3) 形成了嵌套情形。因此可以把逗号表达式扩展为以下形式: 表达式1,表达式2,…表达式n 整个逗号表达式的值等于表达式n的值。 2.程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。 3.并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。 C语言 表达式和语句 一、表达式 前面已经提到过表达式,相信大家对表达式也有了一个初步的认识,它是由常量、变量、运算符组合(到以后讲函数时,函数也可以是组成表达式的元素),计算以后返回一个结果值。表达式的结束标志是分号(;),C语言中所有的语句和声明都是用分号结束,在分号出现之前,语句是不完整的。 例如: 1+2; Counter/3+5; Height*Width; 表达式本身什么事情都不做,只是返回结果值。在程序不对返回的结果值做任何操作的情况下,返回的结果值不起任何作用,表达式的作用有两点,一个是放在赋值语句的右边,另一个是作为函数的参数(以 后再介绍)。 表达式返回的结果值是有类型的。表达式隐含的数据类型取决于组成表达式的变量和常量的类型。因此,表达式的返回值有可能是某种大小的整型,或者是某精度的浮点型,或者是某种指针类型。 这里面就有类型转化的问题了,在前面说整型运算的时候也提到过。类型转化的原则是从低级向高级自动转化(除非人为的加以控制)。计算的转换顺序基本是这样的: 字符型-->整型-->长整型-->浮点型-->单精度型-->双精度型 就是当字符型和整型在一起运算时,结果为整型,如果整型和浮点型在一起运算,所得的结果就是浮点型,如果有双精度型参与运算,那么答案就是双精度型了。 强制转换是这样的,在类型说明符的两边加上括号,就把后面的变量转换成所要的类型了。如: (int) a; (float) b; 第一个式子是把a转换成整型,如果原先有小数部分,则舍去。 第二个式子是把b转换成浮点型,如果原先是整数,则在后面补0。 每一个表达式的返回值都具有逻辑特性。如果返回值为非0,则该表达式返回值为真,否则为假。这种逻辑特性可以用在程序流程控制语句中。 有时表达式也不参加运算,如: if(a||b) ………… 5>3?a++:b++; 当a为真时,b就不参加运算了,因为不管b如何,条件总是真。 二、语句 (一)、赋值语句 其实这个问题,在讲赋值运算符的时候已经讲了一些了。 Amount=1+2; Total=Counter/3+5; Area=Height*Width; 也许你会发现,这些赋值语句很象代数方程,在某些情况下,我们的确可以这样理解,但有时它们是不一样的。看下面: Num=Num+1; 这显然不是一个等式。 (二)、用逗号分隔开的声明语句 C语言可大多数语言一样,允许用逗号分隔声明语句中的标识符列表,说明这些运算符是同一变量类型。例如: float Area,Height,Width; 但有些程序员喜欢把标识符写在不同的行上。如: float Area, Height, Width; 这样写至少有一个好处,就是可以在每个标识符后边加上注释。 在声明变量的时候,也可以直接给变量赋值,这叫做变量的初始化。 如:int a; a=3; 等价于: int a=3; 我们也让某些变量初始化,某些不初始化,如: int a=3,b,c=5; 在进行初始化时,初始化表达式可以是任意的(对全局变量和静态变量有区别),由于逗号运算符是从左到右运算的,那么看看这样行不行, int a=3,b=a,c=5; (三)、标准输入输出语句 Turbo C 2.0标准库提供了两个控制台格式化输入、输出函数scanf();和printf();这两个函数可以在标准输入输出设备上以各种不同的格式读写数据。scanf() 函数用来从标准输入设备(键盘)上读数据,printf()函数用来向标准输出设备(屏幕)写数据。下面详细介绍这两个函数的用法。 1.标准输入语句 scanf()函数是格式化输入函数, 它从标准输入设备(键盘) 读取输入的信息。其调用格式为: scanf("<格式化字符串>", <地址表>); 格式化字符串包括以下三类不同的字符; (1).空白字符:空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。 (2).非空白字符:一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。 (3).格式化说明符:以"%"开始,后跟一个或几个规定字符,用来确定输出内容格式。 Turbo C 2.0提供的输入格式化规定符如下: ?????????????????????????? 符号 作用 ?????????????????????????? %d 十进制有符号整数 %u 十进制无符号整数 %f 浮点数 %s 字符串 %c 单个字符 %p 指针的值 %x,%X 无符号以十六进制表示的整数 %o 无符号以八进制表示的整数 ?????????????????????????? 地址表是需要读入的所有变量的地址,而不是变量本身,取地址符为'&'。各个变量的地址之间同","分开。 例如: scanf("%d,%d",&i,&j); 上例中的scanf()函数先读一个整型数,然后把接着输入的逗号剔除掉,最后读入另一个整型数。如果","这一特定字符没有找到,scanf()函数就终止。若参数之间的分隔符为空格,则参数之间必须输入一个或多个空格。 说明: (a).对于各个变量,类型说明符是什么,输入格式化说明符就应该用对应的类型。否则会出现程序错误或输入数据和理想的不一样。 (b).对于字符串数组或字符串指针变量,由于数组名和指针变量名本身就是地址,因此使用scanf()函数时,不需要在它们前面加上"&"操作符。 char *p,str[20]; scanf("%s", p); scanf("%s", str); 具体字符串,指针的知识以后再介绍。 (c).可以在格式化字符串中的"%"各格式化规定符之间加入一个整数,表示任何读操作中的最大位数。 如上例中若规定只能输入10字符给字符串指针p,则第一条scanf()函数语句变为: scanf("%10s", p); 程序运行时一旦输入字符个数大于10, p就不再继续读入。 实际使用scanf()函数时存在一个问题, 下面举例进行说明: 当使用多个scanf()函数连续给多个字符变量输入时, 例如: char c1, c2; scanf("%c", &c1); scanf("%c", &c2); 运行该程序,输入一个字符A后回车(要完成输入必须回车),在执行scanf("%c",&c1)时,给变量c1赋值"A",但回车符仍然留在缓冲区内,执行输入语句scanf("%c",&c2)时,变量c2输出的是一空行,如果输入AB后回车,那么实际存入变量里的结果为c1为A,c2为B。 要解决以上问题, 可以在输入函数前加入清除函数fflush();(这个函数的使用方法将在本节最后讲述)。 (d).当在格式说明符之间加入'*'时,表示跳过输入,例如: scanf("%3*d",&a); 当输入12345的时候,前面三个字符跳过去不考虑,最终变量a的值为45。 2.标准输出语句 printf()函数是格式化输出函数,一般用于向标准输出设备按规定格式输出信息。在编写程序时经常会用到此函数。printf()函数的调用格式为: printf("<格式化字符串>", <参量表>); 其中格式化字符串包括两部分内容:一部分是正常字符, 这些字符将按原样输出;另一部分是格式化规定字符,以"%"开始,后跟一个或几个规定字符,用来确定输出内容格式。 参量表是需要输出的一系列参数,其个数必须与格式化字符串所说明的输出参数个数一样多,各参数之间用","分开,且顺序一一对应,否则将会出现意想不到的错误。 对于输出语句,还有两个格式化说明符 符号 作用 %e 指数形式的浮点数 %g 自动选择合适的表示法 说明: (1).可以在"%"和字母之间插进数字表示最大场宽。 例如: %3d 表示输出3位整型数,不够3位右对齐。 %9.2f 表示输出场宽为9的浮点数,其中小数位为2,整数位为6,小数点占一位,不够9位右对齐。 %8s 表示输出8个字符的字符串,不够8个字符右对齐。 如果字符串的长度、或整型数位数超过说明的场宽,将按其实际长度输出。但对浮点数,若整数部分位数超过了说明的整数位宽度,将按实际整数位输出;若小数部分位数超过了说明的小数位宽度,则按说明的宽度以四舍五入输出。 另外,若想在输出值前加一些0, 就应在场宽项前加个0。 例如: %04d 表示在输出一个小于4位的数值时,将在前面补0使其总宽度为4位。 如果用浮点数表示字符或整型量的输出格式,小数点后的数字代表最大宽度,小数点前的数字代表最小宽度。 例如: %6.9s 表示显示一个长度不小于6且不大于9的字符串。若大于9,则第9个字符以后的内容将被删除。 (2). 可以在"%"和字母之间加小写字母l,表示输出的是长型数。 例如: %ld 表示输出long整数 %lf 表示输出double浮点数 (3). 可以控制输出左对齐或右对齐,即在"%"和字母之间加入一个"-" 号可说明输出为左对齐,否则为右对齐。 例如: %-7d 表示输出7位整数左对齐 %-10s 表示输出10个字符左对齐 一些特殊规定字符(可以参照前面说的转义字符) ?????????????????????????? 字符 作用 ?????????????????????????? \n 换行 \f 清屏并换页 \r 回车 \t Tab符 \xhh 表示一个ASCII码用16进表示 ?????????????????????????? 由本节所学的printf()函数, 并结合上一节学习的数据类型, 看下面的语句,加深对Turbo C 2.0数据类型的了解。 char c; int a=1234; float f=3.141592653589; double x=0.12345678987654321; c='\x41'; printf("a=%d\n", a); printf("a=%6d\n", a); printf("a=%06d\n", a); printf("a=%2d\n", a); printf("f=%f\n", f); printf("f=6.4f\n", f); printf("x=%lf\n", x); printf("x=%18.16lf\n",x); printf("c=%c\n", c); printf("c=%x\n", c); • 4. 查阅如下一种资料: – (1) 与某种语言(如java、VB等)的编译程序有关 用c语言编de编译程序 for循环语句翻译 递归下降法 输出三地址码 ///////////// #define MAX 100 #include #include #include char str[MAX]; char ch; int turn; char strToken[MAX]; int kind; int n=0;//存放strtoken[]元素的个数 struct Word//结构体 存放单词 { int sort; char word[MAX];//存放strtoken[]的内容 }; //record[x]=new Word; Word *record[12];//放所有识别出来的单词,分别存放他们的编号以及字符串,x是其下标 ////////////////////词法分析/////////////////////// int buffer()//载入 { int i=0; cout<<"输入程序,以―#‖作为结束标志。"<>str[i]不可用,用C语言读入字符。 if(str[i]=='#') break;///////如果尾数为识别码#,则表示程序读完,跳出循环. } break; } return(i); } bool IsLetter(char ch)///////////判断是否是字母 { if(ch>=65&&ch<=90||ch>=97&&ch<=122) return(true); else return(false); } bool IsDigit(char ch)//////////判断是否是数字 { if(ch>=48&&ch<=57) return(true); else return(false); } char GetChar(int i)///////读取字符 { char ch; ch=str[i]; return(ch); } char GetBC(char ch)////判断是不是空格或者换行,如果是,直接读取下一个字符直道不再空 白为止 { if(ch==32||ch==10) { turn++; ch=GetChar(turn); ch=GetBC(ch);/////////递归实现 return(ch); } else return(ch); } void Concat()/////////////连接,即为strtoken[]赋值 { strToken[n]=ch; n++; } int Reserve()/////以单词为单位查找保留字,是则返回编码,不是则返回0,用来区分标志符 和保留字 { if(strcmp(strToken," DIM\0")==0)///////调用strcmp函数实现, return(1); else if(strcmp(strToken,"for\0")==0) return(2); else if(strcmp(strToken,"step\0")==0) return(3); else if(strcmp(strToken,"until\0")==0) return(4); else if(strcmp(strToken,"do\0")==0) return(5); else return(6); } void clear() { n=0; } /////////////*语法递归分析*///////////////// int A(int * c,int & q) { if(c[q++]==3) { if(c[q]==7) { q++; return 1; } else {cout<<"step右部出错"<for S1 do S2"<S2AB"<i=j"<stepj"<untilj"<sort=kind;//12345或6 //cout<word[i]=strToken[i]; cout<word[i];//输出识别的标志符或保留字 } cout<<","<word[i]='\0'; clear(); x++; } else if(IsDigit(ch)) { while(IsDigit(ch)&&turn<=num) { Concat(); ch=GetChar(++turn); } ch=NULL; turn=turn-1; kind=7; ////////////// record[x]=new Word; record[x]->sort=kind; //////////////// cout<<"("; for(int i=0;iword[i]=strToken[i]; cout<word[i]; } cout<<","<word[i]='\0'; clear();x++; } else if(ch=='=') { kind=8; /////// record[x]=new Word; record[x]->word[0]='='; record[x++]->sort=kind; cout<<"(=,"<sort<<" ";//打印单词的编号 。 }cout<sort;//将sort作为数组保存起来 } /////////语法分析/////// int j=0; ///////////////////制导翻译////////////////// if(!S(ana,j)) cout<<"语法出错!"<word[i]!='\0') cout<word[i++];cout<word[0]; i=0; while(record[3]->word[i]!='\0') cout<word[i++];cout<word[i]!='\0') cout<word[i++];cout<<":="; i=0; while(record[1]->word[i]!='\0') cout<word[i++];cout<<"+"; i=0; while(record[5]->word[i]!='\0') cout<word[i++];cout<word[i]!='\0') cout<word[i++];cout<<"<"; i=0; while(record[7]->word[i]!='\0') cout<word[i++]; cout<<" goto 105"<word[i]!='\0') cout<word[i++];cout<<":="; i=0; while(record[11]->word[i]!='\0') cout<word[i++];cout<总结
详解 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下: 从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。 编译过程 编译过程又可以分成两个阶段:编译和会汇编。 编译 编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段: 第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。 主要是以下几方面的处理: (1)宏定义指令,如 #define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。 (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。 (3) 头文件包含指令,如#include "FileName"或者#include 等。 在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。 (4)特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 预编译程序所完成的基本上是对源程序的―替代‖工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。 第二个阶段编译、优化阶段,经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。 编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。 对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。 后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。 汇编 汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段: 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。 UNIX环境下主要有三种类型的目标文件: (1)可重定位文件 其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。 (2)共享的目标文件 这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与 其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。 (3)可执行文件 它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。 链接过程 由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。 例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。 根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种: (1)静态链接 在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。 (2) 动态链接 在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。 我们在linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程: 从上图可以看到: 预编译 将.c 文件转化成 .i文件 使用的gcc命令是:gcc –E 对应于预处理命令cpp 编译 将.c/.h文件转换成.s文件 使用的gcc命令是:gcc –S 对应于编译命令 cc –S 汇编 将.s 文件转化成 .o文件 使用的gcc 命令是:gcc –c 对应于汇编命令是 as 链接 将.o文件转化成可执行程序 使用的gcc 命令是: gcc 对应于链接命令是 ld 总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。Lia了解这四个过 程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链 接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助 的。 – • 5. 用某种熟悉的语言的编译程序来理解层次和遍
/
本文档为【编译原理及实践教程第1章参考答案】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索