从C程序设计的系统总结看现代计算机程序设计的微观.docx
- 文档编号:14580836
- 上传时间:2023-06-24
- 格式:DOCX
- 页数:16
- 大小:43.29KB
从C程序设计的系统总结看现代计算机程序设计的微观.docx
《从C程序设计的系统总结看现代计算机程序设计的微观.docx》由会员分享,可在线阅读,更多相关《从C程序设计的系统总结看现代计算机程序设计的微观.docx(16页珍藏版)》请在冰点文库上搜索。
从C程序设计的系统总结看现代计算机程序设计的微观
一.从C程序设计的系统总结看现代计算机程序设计的微观
⒈应如何看待高级程序设计语言中的数据类型
·高级程序设计语言中的数据类型的种类与计算机硬件所能支持的数(位数)据存储构造有关;
·高级程序设计语言中的数据长度与计算机的机器字长和指令种类有关;
·编译器和程序编译产生的程序处理同一数据类型的过程存在差异;
数据类型符号与机器字长的关系对照表
数据类型符号
所对应的机器字长
int、unsignedint
整数字(有、无符号)
short、unsignedshort
整数半字(有、无符号)
long、unsignedlong
整数双字(有、无符号)
char、unsignedchar
整数字节(有、无符号)
float
浮点数字
double
浮点数双字
longdouble
浮点数四字(倍双字)
例1:
已知某计算机机器字长16位,试指出下述C语句的执行结果。
inti=40000;/*若字长是32位,可以令i=2150000000*/
printf(“%d”,i);
分析:
先将i变换为十六进数得:
40000=0X9c40(若字长是32位且i=2150000000,则i变换的十六进数值为9X80266580)
∵i为有符号整数(即将占用一位数的符号位)且符号位为1(即为负数)
∴编译系统将计算其视为补码(即-0X63c0),因此执行后的输出结果为-25536
造成思路偏差的关键是数在编译转换与输出转换是先后两个不同的环节。
很难有学生能将在内存中的数据处理与输入输出时进行的数据转换之间的关系想明白。
例2:
已知某程序内的初始数据声明为:
char*p=”acd”;当该程序运行到某时刻时,使指针指向字符串内字符‘c’的位置,试判断下述四条C语句中哪一条能够正确取得该字符串内的字符‘a’?
(A)*p--;(B)*--p;(C)*p-1;(D)*(p--)
分析:
地址数值为同长、同类型数据,故该计算将涉及到多重转换的优先策略(即地址变更与对地址的一次间址访问的优先策略)。
当复合发生地址变换与访内操作时则要是各自运算符号的相对位置而定。
因此可很容易的判断出(B)是正确的语句。
辨别该问题正确解答的关键是正确理解间接寻址和多元访问的顺序规则。
例3:
试说明C语句“i+++j;”的计算策略。
分析:
编译扫描自左向右,故判断操作对象与运算的关系应形成”i++”和’j’两个部分,又”i++”为一元运算,故先予计算。
若编译器的扫描方向逆转,则先计算++j也是正确的。
·使用不同的数据类型与数据长度时应考虑其相互间的(编译)变换规则;
例4:
试判断出下述C程序的执行结果:
16位字长的代码:
32位字长的代码:
longs,l=5;longs,l=5;
unsignedintu=4;unsignedshortu=4;
charc=-3;/*0Xfd*/charc=-3;/*0Xfd*/
s=l+u*c;/*预期值为-7*/s=l+u*(unsignedshort)c;/*预期值为-7,但实际值为262137,即0X3FFF9*/
printf(“%ld”,s);printf("%ld",s);
分析:
u*c中的c先转换成无符号整数(即0Xfffd),然后完成u*c计算(乘4即左移两位)并获得中间结果0Xfff4。
为计算l与该中间结果的和则先使无符号整数的中间结果转换为与l相同的有符号双字(即0X0000fff4),则s=65529(即0X0000fff9)。
与预期值不一致的根本原因在于c经历了两次符号间的转换。
如果在开始计算之前便使所有计算量值都一致,则此问题就可以避免了。
既将“s=l+u*c;”语句改为“s=l+(long)u*c;”。
在近年来的编译器中可以省略强制。
本例的思考点:
·任何编译器所支持的数据类型转换策略都只能适用于有限的环境;
·在对所使用的软件开发工具具有的多种转换策略不了解的情况下开放获得的软件产品存在缺陷是必然的;
本例中所用到的不同数据类型转换策略如下:
①无符号整数之间
短→长:
高位补零
长→短:
截去高位
②有符号整数之间
短→长:
正数高位补零;负数高位补一
长→短:
截去高位
由此可以得到以下结论:
·使用不同的数据类型的混合计算必须考虑由编译系统预先设定的变换策略(如长度转换策略、符号转换策略、数值精度转换策略、多重处理转换的优先策略等);
·使用不同的运算符号所组成的混合数据类型计算,编译器会安依照算符元数优先策略生成机器代码;
·指针是地址数据,经指针的访内操作是机器指令的间址操作;
P
·指针类型是用来决定指针所代表的地址数据的变址计算单位的;
·理论上不同类型的指针也可以强制,但从安全观点考虑推荐用小刻度地址单位的指针强制指向较大刻度的指针;
例5:
若机器字长为16位,试判断下述C语句的执行结果。
longl=1;
char*p=(char*)&l;
printf(“%x”,*p);/*若将p分别改为p+1和p+3,则结果如何?
*/
分析:
在机器字长为16位的计算机内,long类型占用双字长度,而当对其进行字节类型的char强制时,将按字节取出对应内存的数据。
因long类型数据的内存布局如下所述,故输出结果为1。
低位字高位字
低位字节高位字节低位字节高位字节
·在C语言内的数组与指针的处理机制大体相同,根本区别是数组是只读的指针;
⒉从函数的引用看不同执行程序单元间的数据交换
·从软件程序开发的过程看,必须先声明(确定名称和数据类型)、再定义(赋予名称的实体意义)、最后引用(执行程序的机器代码);
·使用参数单向传递是出于保护各自过程作用域数据的目的;
·使用全程变量或指针作参数可以实现数据的双向传递(但不能定制);
·函数的返回数据若是指针,则可以对其进行任何指针允许的操作;
例6:
试判断下述程序执行后的输出结果。
intpub=0;
int*f(inta,intb)
{
pub=pub+a+b;
return&pub;
}
voidmain()
{
*f(3,4)+=5;
printtf(“%d”,pub);
}
分析:
对执行f函数后所返回的变量pub的地址作间址访问得到其内容再与被加数相加,等效于3+4+5=12。
·C/C++程序内函数与数据具有等价的地位(函数型指针为典型代表);
函数型指针的声明格式:
返回的指针数据类型(*函数指针名)(参数类型,…);
例7:
函数型指针的应用示例
doublemax(int,int);
doublers;
double(*pf)(int,int);
…
pf=&max;/*函数地址附值*/
…
rs=(*pf)(3.14,1.22);
rs=pf(9.0,2.4);/*函数指针引用的简化形式*/
·在C程序内函数型指针多以函数的参数出现在虚函数中;
虚函数的声明格式:
返回类型函数名(参数类型,…,指针类型(*)(参数类型,…));
虚函数的定义格式:
返回类型函数名(参数名1,…,指针类型(*虚函数名)(参数类型,…)){…代码体}
例8:
利用虚函数设计的求圆周长和面积C程序代码
#include
constfloatpai=3.14159;
floatcircle_len(floatradio){returnradio*pai*2;}
floatcircle_area(floatradio){returnpai*radio*radio;}
floatcircle_js(floatradio,float(*f)(float)){returnf(radio);}
voidmain()
{
floatr1=1.,r2=2.;
printf("Radio=%flength=%f\n",r1,circle_js(r1,circle_len));
printf("Radio=%farea=%f\n",r2,circle_js(r2,circle_area));
getchar();
}
⒊有限程序作用的概念
·程序加工的对象是数据;
·数据在其生命周期内分为全程(局即global)和局部(local);
·某个特定数据只与某段程序的特定处理相关,最具体的体现为C/C++语言的附加存储类别和运行期的变量生命周期的控制;
①C语言内四种附加存储类别
·extern—所引用的数据在作用域之外声明;
·static—所引用的数据的生命周期不受声明作用域的影响;
·auto—所引用的数据的生命周期仅限于声明作用域内;
·register—所引用的数据须在寄存器内完成计算;
例9:
试判断执行该程序后的结果
inti=0;
voidmain()
{
inti=1;
printf(“%d”,i);
}
分析:
两个i的生命周期别分是global和local,则其对应的附加存储类别分别是static和auto。
因printf函数处在main函数的作用域内,所以首先访问该域内的i变量,故输出结果为1。
②编译器提供的内存模式
内存模式是根据不同的程序代码与数据规模而预先设定的程序在运行时的内存驻留格局。
内存模式通过C编译附加参数设定。
·根据代码和数据的内存驻留格局可以分为tiny、small、medium、compact、large和huge等模式;
十六位计算机为例的C编译模式表
模式
函数代码段
数据段
默认调用的库名
段数
段地址长度
段数
段地址长度
Tiny
唯一
2B
与代码段共用一段
SLIBCE/C7
Small
唯一
2B
唯一
2B
SLIBCE/C7
Medium
多个
4B
唯一
2B
MLIBCE/C7
Compact
唯一
2B
多个
4B
CLIBCE/C7
Large
多个
4B
多个
4B
LLIBCE/C7
Huge
多个
4B
多个
4B
LLIBCE/C7
·当只是一个局部的数据需要改变内存驻留格局时可以使用编译附加内存模式保留字通知编译器(C的内存模式保留字有near、far、huge);
在使用时应遵守如下约定:
·同类的附加关键字要一致
例10:
intfarad(int,int);
intfarsb(int,int);
intrelt(int,int,int(far*f)(int,int));
·三个模式附加关键字只能顺向转换
既near→far→huge
·三个模式附加关键字不能改变已有的系统(库)函数的返回值类型
例11:
doublefarsin(double);
voidmain()
{printf(“%f”,sin(.5));}/*显示结果为:
0*/
③直接访内
C语言使用段和段基语句可直接访内。
段和段基语句的关键字为:
_segment段名=内存基地址;
_based(段名)段偏移名=偏移值;
与其配套的算符为:
:
>
例12:
_segmentsg=0xb800;
unsignedchar_based(sg)*p=0;
inti;
p++;
for(i=0;i<2000;i+=2)*(p+i)=0x84;
简单的彩色属性:
若使用配套的算符则*(p+i)=0x84一项可改写为:
*(sg:
>p+i)=0x84;
二.一些C语言应用知识的扩充
⒈C与X86汇编语言的混合编程技术
1大量简单而又重复的运算和控制处理
例13:
可将前一例改为如下的嵌入式汇编程序段:
_asm{pushes
movax,0b800h
movees,ax
movcx,2000
movdi,1
moval,84h
cld
xx:
stosb
incdi
loopxx
popes
}
2特定的端口时序控制
例14:
已知主频为40MHz(即周期为0.025μs)的某接口,欲对其最低位打入一个1μs的脉冲,可在C程序中嵌入如下的汇编程序段:
_asm{movdx,0a80h
anddx,0feh
orax,1
outdx,ax
movcx,3
xx:
nop;…………………3T
loopxx;…………8T
andax,0feh;……4T
outdx,ax;………3T
}
3用作宏以便提高运算的速度
例15:
#definesq(x,k)_asm{\
_asmmovax,x\
_asmcmpax,256\
_asmja$+7\/*2*/
_asmimulal\/*2*/
_asmmovk,ax\/*3*/
}
voidmain()
{
inti=3,j;
sq(I,j);
printf(“%d”,j);
}
4系统调用
例16:
_asmmovah,2
_asmmovdl,7
_asmint21h
⒉预处理的作用
目的:
在C编译阶段实现一些区段动态的符号替换。
格式:
#预处理指示关键字[参量……]
几个常用的预处理语句
①嵌入式替换预处理语句include
#include<……>
#include”……”
②符号替换预处理语句define
⑴常数替换
例:
#definepai3.14
#definepai2(pai+pai)
常数不是常量;括号不能少;
⑵变量替换
例17:
#defineab
例18:
#definenlprintf(“\n”)
#definenl3nl;nl;nl
#definenl9for(i=0;i<9;i++)nl
⑶宏
C语言中的宏实际上是在编译之前由编译器完成的一种符号集群的替换。
因此,切不可将之与函数混淆。
依据替换位置的不同,C语言中的宏又有所谓宏函数和宏过程之分。
如:
#definemax(x,y)((x)>(y))?
(x):
(y)是宏函数
而:
#definepti(n)printf(“%d”,n)和#definept(s)printf(“s”)就是宏过程
使用宏的最大问题是容易产生二意性。
例19:
#definesq(x)((x)*(x))
voidmain()
{
inti=3,j;
j=sq(i++);
printf(“%d%d”,j,i);/*显示结果为:
12而不是9*/
}
⑷代名符
代名符是一种特殊的常数代换。
例20:
#defineTEST
TESTvoidmain()
{……}
⑸在替换中使用参数
由于#define语句本身的作用就是在通知编译器如何进行各种符号的替换,为了满足未来运行时将出现的符号中的符号替换需求时,C语言还专门提供了一个特殊的符号作为未来的参数标识,被称为符号参数。
符号参数用“#”和“##”表示。
前者意为在引用处直接替换;后者意为在引用处的变量名续接。
例21:
#definepr(x)printf(#x"\n")
#definepai13.14
#definepai26.28
#definepaiout(x)printf("%f\n",pai##x)
……
voidmain()
{
……
pr(abc);/*输出结果为:
abc*/
paiout
(2);/*输出结果为:
6.28*/
}
1撤消符号替换预处理语句undef
例22:
#definealfa(x,y)……
void(……);
voidmain()
{……}
#undefalfa
#definealfa2.72
voidsb(……)
{……}
⒊内存的动态分配
①实模式下C程序的静态内存布局
CS→
程序代码段
公共DS→
全程、静态分配区(static)
默认DS、SS
→
可被动态分配的程序内数据区(nearheap)
SP(顶)
本地栈(stack)
局部动态数据占用区(auto)
可被动态分配的外部堆数据区
(farheap)
两者均为堆栈区,默认容量为2048字节
·堆栈区可以通过编译参数设定,但最大不超过32KB;
·动态的占用不同位置的内存需通过使用不同的内存分配库函数实现;
②内存的动态分配库函数的应用
内存的动态分配库函数均由位于malloc.h的头文件中予以声明。
(1)动态占用本地堆栈区的库函数void*alloca(int);
特点:
最大容量小于32KB;无须使用库函数释放;
(2)动态占用程序内数据区的库函数void*_nmalloc(unsignedint);
特点:
最大容量可达64KB;要使用库函数“void_nfree(void*)”释放所占内存;
(3)动态占用外部数据区的库函数voidfar*_fmalloc(unsignedint);
特点:
最大容量视剩余内存容量而定;以64KB为单位自动分段;要使用库函数“void_ffree(voidfar*)”释放所占内存;
(4)自动适应动态占用某一空闲内存的库函数void*malloc(long);
特点:
可依据空闲内存的不同位置自动分配;所分配的内存须用库函数“voidfree(void*)”释放;
重要的结论:
综上所述,学好程序设计需要四个的重要前提知识或能力基础:
·对冯·诺依曼计算机内存储器的分类、存储与访问特性的清晰理解;
·具备一定程度的数学逻辑思维能力解决算法如何呈现在最优的代码中的问题;
·对程序设计工具的灵活运用能力;
·对程序未来运行的系统环境的一些相互作用因素的了解;
本部文档重点讨论了其中的第一点,简单讨论了第三点。
第二点多在本科的一些课程内程度不同的予以体现。
而后两点需要在具体工程实践中通过交流和摸索来掌握。
因此培养实践能力的成本也是很高的,应当引起学习者足够的重视。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 程序设计 系统 总结 现代 计算机 微观