您好,欢迎访问三七文档
第1章扩展内容C程序的词素词素(token)由一个或者多个字符构成,是构成源程序的基本词汇。C语言的词素分六类:关键字(keyword,保留字)、标识符(identifier)、常量(constant)、字符串常量(stringconstant)、运算符(operator)、分隔符(punctuator)。例如:#includestdio.hmain(){intpen,eraser,sum;pen=12;eraser=6;sum=pen+eraser;printf(Thesumis%d\n,sum);}关键字是C语言中有确定含义的单词,例如int是关键字,其在C语言中的含义是确定的,表示整数类型。标识符是程序员自己定义的单词,变量名、函数名、数据类型名等都属于标识符。例如:源程序中的变量名pen、eraser、sum都是标识符。main是主函数的名字,它的含义虽然是确定的,但也属于标识符。printf是库函数的名字,也属于标识符。源程序中的12和6表示确切的整数数值,属于int型常量。Thesumis%d\n表示确切的文字内容,属于字符串常量(一串文本数据),字符串常量需要用双引号引起来。+属于运算符,参加运算的操作数称为源操作数,结果称为目的操作数。main后面的()也是运算符,表示main这个标识符是函数的名字。大括号、逗号和分号属于分隔符。C语句与机器语言指令的关系在C语言中,语句是计算机可以执行的基本操作,相当于计算机可以理解并执行的命令。但从计算机原理上说,C语言的语句不能被计算机直接执行,计算机可以直接执行的是机器语言指令(二进制形式的命令)。C程序中的语句在编译的时候将翻译成机器语言指令。机器语言指令通常包括两部分:操作码和操作数。操作码指明的是操作的性质,每种操作对应一个操作码。比如说,加减乘除属于不同的操作,那么它们对应的操作码也不相同。指令中的操作数可以是直接参加运算的数值,也可以是实际数值的存储地址。例如:某A是一台功能非常简单的计算机,其可以执行的指令只有4种:(1)内存中两个操作数相加,结果保存在内存;(2)内存中两个操作数相减,结果保存在内存;(3)内存中两个操作数相乘,结果保存在内存;(4)内存中两个操作数相除,结果保存在内存。在设计机器的时候,其可执行的4种指令的操作码必须不同,比如说,可以把A机器的操作码设计为:加00000000减00010000乘00100000除00110000在设计机器的时候,还必须设计指令的格式,例如机器A的指令格式为:操作码源操作数1源操作数2目的操作数此时,如果机器A要把内存中地址为1000H和2000H处的操作数相加,结果保存在3000H处,则执行的指令为:00000000000100000000000000100000000000000011000000000000其中,00000000为操作码,灰色背景的3个二进制数据为3个操作数的地址。C程序的编译C程序不能被计算机直接理解,必须编译成机器语言程序之后才能被计算机执行。C程序中的变量在编译之后就是内存单元①。例如:inta,b,c;…c=a+b;程序段有3个整型变量,在编译之后的机器语言程序中,3个变量将对应3个内存单元。假设3个内存单元的地址分别为:1000H,1004H,1008H,则语句c=a+b将被翻译成:00000000000100000000000000010000000001000001000000001000必须说明的是:(1)上面的涉及的语句比较简单,1条语句翻译成1条机器指令,C程序中功能比较强大的语句,可能要翻译成多条机器语言指令;(2)把C程序编译成机器语言程序由软件完成,通常我们称这类软件为编译器。移植移植指的是在一种计算机上运行的软件不需要修改就可以在另外类型的计算机上运行。移植可分为:机器语言代码级的移植、源代码级的移植。所谓机器语言代码级的移植,就是说编译之后产生的机器语言程序可以在另外类型的计算机上运行。例如:机器B是一台功能简单的计算机,其可以执行的指令有4种,与机器A相同。但机器B的操作码设计方案与机器A不同,机器B的操作码设计方案为:加10100000减10110000乘11000000除11010000显然,我们上面提到的机器语言指令00000000000100000000000000010000000001000001000000001000可以在机器A上执行,但不能在机器B上执行。因为该指令的操作码00000000不能被机器B理解。①严格地说,编译之后变量只是逻辑空间中的内存单元,只有在执行的时候,计算机内的实际内存空间才会存储变量的数值。所谓源代码级的移植,就是说相同的源程序分别在机器A和机器B上编译,产生的机器语言程序可以在机器A和机器B上运行。此时,两台机器上的源程序相同,但机器语言程序不同。比如说,我们编写的C语言源程序中有以下语句:c=a+b;在机器A上该语句翻译成:00000000000100000000000000100000000000000011000000000000在机器B上该语句翻译成:10100000000100000000000000100000000000000011000000000000显然,翻译后的指令可以分别在机器A和机器B上运行。这种情况就属于在源代码级可以移植,但在机器语言代码级不可以移植。第2章扩展内容内存的编址单位与编址方式整型、浮点型和字符型属于基本数据类型,也称为原子数据类型。原子类型的数据在内存的存储细节与内存的编址单位和编址方式有关。通常,计算机的最小编址单位是字节,但也有系统的最小编址单位是字。比如说,某计算机的内存容量为1GB,编址单位是字节,则内存地址为:0~(230-1)。也就是说,内存中每个字节的存储空间都有一个地址。再比如,某计算机的内容容量为1GB,字长为32位(4个字节),编址方式为字,则内存地址为:0~(228-1)。在该计算机中,内存中每个字的存储空间有一个地址。由于大多数计算机的最小编址单位是字节,所以本书下面的内容都假设最小编址单位是字节。内存的基本编址方式有两种:高端编址(大端编址,big-endian)和低端编址(小端编址,little-endian)。所谓高端编址,就是说数据的高位存储在内存单元的起始位置。所谓低端编址,就是说数据的低位存储在内存单元的起始位置。例如:longinta=0x11223344;longintb=0x55667788;无论是在32位编译环境中,还是在16位编译环境中,int型变量都占据4个字节的存储空间,假设变量a的内存地址为1000H,变量b的内存地址为1004H,则高端编址的存储细节如左图所示,低端编址的存储细节如右图所示。0123…230-1字节编址方式0123…228-1字编址方式1字节4字节C语言层次上的数据与机器语言层次上的数据在C程序中,简单的数据通常存储在变量中,变量在使用之前必须首先声明,声明的时候必须指明变量的数据类型。从C语言层次上说,变量既包含数值信息,也包含类型信息。也就是说,在C程序员的眼中,数据本身是带有类型描述的。但从机器语言的角度看,内存中存储的数据并不包含数据的类型描述;换句话说,根据内存中存储的二进制形式的数据无法获知数据属于什么类型。例如:shortinta=-1;shortunsignedintb=65535;有符号短整数a和无符号短整数b在计算机内存的二进制形式均为:1111111111111111,二者没有任何不同。而且内存也没有存储任何描述它们的数据类型的信息,因此机器语言程序单纯根据数据的二进制形式,并不能推知数据的类型。在机器语言层次上,计算机可以执行的基本操作称为指令。指令包括两部分:操作码和操作数。操作码除了要指明操作性质(加减乘除等)之外,还要指明操作数的类型。操作数则指明参加运算的二进制数据,或者该二进制数据的地址。比如说,我们在第1章扩展内容中提到的机器A,是功能非常简单的计算机。在稍微实用一点的计算机中,其加减乘除等每类指令不止一条。例如:机器A的乘法指令可能有2条,分别是有符号整数相乘和无符号整数相乘,它们对应的操作码可设计为:有符号乘00100000无符号乘00100001那么,把内存1000H处和2000H处的有符号整数相乘,结果保存到3000H处,则机器语言指令可能为:00100000000100000000000000100000000000000011000000000000其中前8位是指令的操作码,表示操作性质为有符号整数相乘,后面为3个操作数的地址。如果把内存4000H处和5000H处的无符号整数相乘,结果保存到6000H处,则机器语言100011100122100233100344100455100566100677100788高端编址100044100133100222100311100488100577100666100755低端编址指令可能为:00000001010000000000000001010000000000000110000000000000其中前8位是指令的操作码,表示操作性质为无符号整数相乘,后面为3个操作数的地址。注意:(1)虽然都是乘法指令,但指令的操作码并不相同;(2)虽然操作数的实际类型不同,但指令中的操作数在形式上没有什么区别。也就是说,数据类型的不同是由操作码指明的,而不是由操作数本身指明的。如果我们编写C程序,首先计算两个有符号短整数的乘积,然后计算两个无符号短整数的乘积。程序的相关代码可能为:shortintx,y,z;shortunsignedinta,b,c;…z=x*y;c=a*b;显然,从C程序员的角度看,两个乘法运算符没有任何区别,但操作数(变量)是有区别的,x,y,z属于有符号短整数,a,b,c属于无符号短整数。当编译器把C程序翻译成机器语言程序的时候,编译器不仅要根据具体的运算性质,还要根据操作数的类型选择指令。当翻译z=x*y的时候,编译器根据x,y,z的声明选择有符号短整数乘法指令。当翻译c=a*b的时候,编译器根据a,b,c的声明选择无符号短整数乘法指令。如果x,y,z,a,b,c分别存储在地址为1000H、2000H、3000H、4000H、5000H、6000H的内存单元,则翻译后的相关机器代码为:0010000000010000000000000010000000000000001100000000000000100001010000000000000001010000000000000110000000000000总结:在机器语言层次上,操作码指明操作数的类型,操作数自身并不能描述自己的数据类型;在C语言层次上,操作数指明本身的数据类型,运算符并不指明操作数的类型。C语言数据类型转换产生的例外结果教材《程序设计基础(C语言)》的附录B介绍了C语言的数据类型转换规则,C语言的数据类型规则转换比较复杂,甚至有时候会产生让人觉得不可思议的结果。例如:unsignedlongua=1;longa=-1;if(aua){printf(Normal\n);}else{printf(NotNormal\n);}程序段的结果似乎是确定的,a的数值为-1,ua的数值为1,aua应该是成立的。但如果运行程序,我们会发现程序的结果并非如此,在C程序中aua竟然不成立。早成这种情况的原因就是C语言的类型转换规则。a的数据类型为long,即signedlong,ua的数据类型为unsignedlong。当执行关系运算aua的时候,两个操作数的类型不一致,a的数值将转换成unsignedlong类型。在计算机中,a的二进制形式为:11111111111111111111111111111111。根据附录B介绍的数据类型转换原理,转换之后的数据的二进制形式仍然如此。但问题是转化之后成了无符号数据,该无符号数据表示的数值为232-1=2147483647。显然,这个数值比1要大,因此程序认为:aua是不成立
本文标题:扩展内容
链接地址:https://www.777doc.com/doc-3129150 .html