1 / 26

第 9 章 编程技巧

课件制作:刘达明 023-66834110. 《C 语言程序设计 》 龙昭华主编. 第 9 章 编程技巧. 第一节 表达式解释计算 第二节 C 与操作系统接口设计 第三节 C 与汇编语言的接口 第四节 程序调试问题. 1 / 26. 数字. 数: num(). 数字. 数. 因子: factor(). (. ). 表达式. 因子. 项: term(). * /. 因子. 项. 表达式: expr(). + -. 项. 课件制作:刘达明 023-66834110. (第九章 编程技巧). 第一节 表达式解释计算.

laurel
Download Presentation

第 9 章 编程技巧

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 课件制作:刘达明 023-66834110 《C语言程序设计》龙昭华主编 第9章编程技巧 第一节 表达式解释计算 第二节 C与操作系统接口设计 第三节C与汇编语言的接口 第四节 程序调试问题 1 / 26

  2. 数字 数:num() . 数字 数 因子:factor() ( ) 表达式 因子 项:term() * / 因子 项 表达式:expr() + - 项 课件制作:刘达明 023-66834110 (第九章 编程技巧) 第一节表达式解释计算 一、表达式句法定义 需要定义一个含有+、-、*、/的运算表达式。加、减、乘、除四则运算符及 括号的意义完全与平常的算术运算习惯一致。程序接受一行字符串,并对它进 行表达式的解释计算,这就相当于一个简单的编译系统。 2 / 26

  3. 课件制作:刘达明 023-66834110 第一节表达式解释计算 (第九章 编程技巧) 二、各句法对应函数 为了便于识别表达式的各成份,并计算它们的值,需要定义相应的函数: num()函数:将数的字符序列转换成数值。如:9、8.12、-10.30、1230等。 factor()函数:计算因子的值。如:-91.89、(90*10.8+5.8)、60等。 term()函数:计算项的值。如:89、(9/2+10)、90*7/10、(10-2)/2等。 exor()函数:计算表达式的值。如:9+(10/2-9)、200-90、30+0.99等。 由于表达式句法的递归定义,这些函数也将通过相互递归调用求得各自对应 成份的值。 假设主函数控制流程是反复读入一行正文,调用表达式计算函数expr(),求 出表达式的值并输出,直至输入空行结束。 程序中引入下列全局变量:buf[]字符数组,存储一行正文信息。cpt为当前 正待识别的字符指针。result为存储表达式计算结果的变量。主函数的算法为: ①读入一行存buf数组。②如果输入行非空转③,否则转⑦。③置cpt初值。④ 计算表达式值result=expr();⑤输出result的值。⑥转①。⑦退出。 在第4步中所用到的函数需要编写,其余函数用标准库函数完成。 3 / 26

  4. 课件制作:刘达明 023-66834110 第一节表达式解释计算 (第九章 编程技巧) 1、expr()函数算法 根据表达式的句法定义,expr()函数有以下形式: double expr() { value1=term(); /*调用函数term()求项值*/ while(当前有效字符为‘+’或‘-’) { 保存运算符; value2=term(); /*求下一个项值*/ if(运算符==‘+’) value1+=value2; else value1-=value2; } return value1; } 说明: 当前有效字符是指 非空白字符。 函数term()的算法与函数expr()的算法类似。函数term()调用factor()完成 因子计算,处理乘除法运算符‘*’和‘/’。对于除法需要考虑除数为0的情况。 4 / 26

  5. 课件制作:刘达明 023-66834110 第一节表达式解释计算 (第九章 编程技巧) 2、factor()函数算法 ⑴当前有效字符为数字符,因子是一个数。 转换该数的字符序列为数值,作为因子的值。 ⑵当前有效字符为左括号‘(’,因子是一个带括号的表达式。 递归调用函数expr(),以表达式的值作为因子的值。且表达式计算后,当前 有效字符应为右括号‘)’,否则因括号不正确配对,是个句法错误的表达式。 ⑶当前有效字符为其它字符,也是个句法错误的表达式。 else 报告表达式句法错误; return fvalue; } else { 报告表达式句法错误; return 1.0; } } double factor() { if(当前有效字符为数字符) return num(); if(当前有效字符==‘(’) { 移动字符指针至下一个有效字符; fvalue=expr(); if(当前有效字符==‘)’) 移动字符指针至下一个有效字符; 注意: 这里没 有讨论 有‘)’而 没有‘(’ 与之匹 配的问 题。 5 / 26

  6. 课件制作:刘达明 023-66834110 第一节表达式解释计算 (第九章 编程技巧) 三、完整的程序 3、num()函数算法 num()函数实际上就是字符转换成数值。先转换整数部分,整数每位数字乘 以10,使用循环完成。如果还有小数点‘.’,则小数点后面的数字乘以0.1,仍使 用循环完成。 例9.1 表达式解释计算的详细程序见li9_1.c。 运行结果: 9+8 (enter) he result is 17.000000 (10-20)*2+5 (enter) he result is -15.000000 9+9*9+1-(10/2+1.9) (enter) he result is 84.100000 (enter) 注:最后一个回车退出循环。 6 / 26

  7. 课件制作:刘达明 023-66834110 (第九章 编程技巧) 第二节C与操作系统接口设计 一、BIOS中断表 在C语言编程中,常常需要利用操作系统如DOS的低级资源,而这些资源不能 使用C编译程序访问,必须使用DOS中断(软中断)才能完成。 中断是一种特殊类型的指令,它停止执行当前程序,把系统当前状态保留在 堆栈中,然后转移到由中断号确定的相应的中断处理子程序上。当中断子程序 执行完时,它执行中断返回,使原先运行的程序恢复执行。中断分为硬中断和 软中断两种基本类型。8086CPU允许程序通过INT指令执行软中断,跟在INT指令 后的数字指定所用的中断号。如:INT 21h执行21h号中断。中断号是用来找出 相应的中断处理程序的。 在PC-DOS中,使用软中断访问操作系统功能,每个中断命令都有其专门的 访问功能类型,而且这些功能函数是由AH寄存器中的值决定的。。如果需要增 加信息,所增加的信息传给AX、BX、CX和DX寄存器。 PC-DOS操作系统分为ROM-BIOS(Basic I/O System)和DOS(Disk Operating System)两部分。ROM-BIOS提供最低层子例程,而DOS用这些低层子例程提供进 一步的高级功能。二者是交叉在一起的,用户访问它们的方法基本相同:都是 通过软中断。 7 / 26

  8. 中断号 中断号 功 能 功 能 盒带控制 15h 屏幕打印服务程序 5h 16h 键盘I/O 显示器I/O 10h 17h 设备清单 11h 打印机I/O 内存大小 12h 18h 执行ROM BASIC 19h 磁盘I/O 执行引导装入程序 13h 系统时间和日期设置 1Ah 14h 串行口I/O 课件制作:刘达明 023-66834110 第二节C与操作系统接口设计 (第九章 编程技巧) BIOS中断表(续) 在PC-DOS中,ROM-BIOS有12个中断,如下表所示: 访问上表中断有两种方法:一是使用系统调用函数int86(),二是使用汇编语言接 口来实现。大多数C都提供了int86()函数。int86()函数的一般格式如下: #include <dos.h> int int86(int intnum, union REGS *in, union REGS *out) 8 / 26

  9. 课件制作:刘达明 023-66834110 第二节C与操作系统接口设计 (第九章 编程技巧) 二、利用int86()函数访问BIOS系统功能 在int86()中需要用到的结构体与共用体如下: struct BYTEREGS { unsigned char al, ah; unsigned char bl, bh; unsigned char cl, ch; unsigned char dl, dh; }; /*寄存器字节*/ /*字符寄存器*/ struct WORDREGS { unsigned int ax, bx, cx; unsigned int dx ,si, di unsigned int cflag, flags; }; union REGS { struct WORDREGS x; struct BYTEREGS h; }; 例9.2利用10h号中断功能6,可以实现清屏。 #include <dos.h> void cls() { union REGS r; r.h.ah=6; /*屏幕转动程序*/ r.h.al=0; /*清屏程序*/ r.h.ch=0; /*上转起始行*/ r.h.cl=0; /*列起始*/ r.h.dh=24; /*上转结束行*/ r.h.dl=79; /*列结束*/ r.h.bh=7; /*空行是黑色*/ int86(0x10,&r,&r); } 9 / 26

  10. sc[0] sc[1] int c; char sc[2]; 课件制作:刘达明 023-66834110 第二节C与操作系统接口设计 (第九章 编程技巧) 例9.3 调用16h号中断0号功能读取键盘扫描码。 在为IBM-PC及其兼容机编程时,最难读到的是箭头键和功能键,以及INS、 DEL、PGUP、PGDN、END、HOME等键的ASCII码值。键值存放如下: 当用户在IBM-PC机上按下一个键时,产生一个称为扫描码的两个字节(16位) 的值。该扫描码由两部分组成:低位字节内含相应键的ASCII码(若它是标准键), 高位字节内含该键在键盘上的定位码。对于标准键的定位码为0,因此低位的值 就是它的ASCII码值(8位)。而对于特殊键,它的低位值为0,高位的值才是它的 键值。要得到特殊键值,不能使用gets()、scanf()等函数,只能用这里提供的方 法。其程序参见li9_3.c。运行结果显示部分键盘扫描码为: A-65 a-97 0-48 9-57 (-40 +-43 <-60 =-61 左箭头-75右箭头-77 上箭头-72下箭头-80 INS-82 DEL-83 F2-60 F3-61 F10-68 ESC-27 PGUP-73 PGDN-81 END-79 HOME-71 int get_key() { union REGS r; r.h.ah=0; return(int86(0x16,&r,&r)); } 10 / 26

  11. 课件制作:刘达明 023-66834110 第二节C与操作系统接口设计 (第九章 编程技巧) 三、利用DOS访问系统功能 PC-DOS中由ROM-BIOS引导装入程序装入和执行的部分叫DOS。其中包含了大 部分在ROM-BIOS例程中找不到的各种各样的高级功能,利用AH寄存器传送所请 求的DOS功能调用号,通过中断21h可以访问DOS的所有功能。如1号功能为从键 盘读字符,2号功能为在屏幕上显示字符,3号功能从异步端口读字符,4号功能 写字符到异步端口,5号功能在打印机上打印字符,B号功能检查键盘状态,2A 号功能读取系统日期,2B号功能设置系统日期,2C号功能读取系统时间等。 虽然可以像ROM-BIOS功能一样,利用int86()函数访问DOS功能,但很多系统 都有一专门函数bdos(),该函数用来执行21h号中断调用,调用操作系统中的某 个高级功能。 bdos()函数原型如下: int bdos(int fnum,unsigned int Reg_DX,unsigned int Reg_AL) 其中:fnum是DOS功能号; Reg_DX的值赋给DX寄存器; Reg_AL的值赋给AL寄存器; 返回值:bdos()回送AX寄存器的值。 11 / 26

  12. 课件制作:刘达明 023-66834110 第二节C与操作系统接口设计 (第九章 编程技巧) 例9.4 调用12h号中断Bh号功能检查键盘状态。 除了第一个参数外。其余都用0,因为不 需要其它信息。把返回值强行变成char型 是必要的,因为所返回的是在AL中的状 态,而AL没有定义。 #include <dos.h> int kbhit() { return((char)bdos(0xB,0,0)); } kbhit()函数的返回值为:如果按下键,则返回“真”,否则返回“假”。kbhit()函数 的一个非常普通的用途就是可以让某个子例程被用户命令所中断。 例9.5调用12h号中断的3号功能读串口,4号功能写串口。如果需要编写一个调 制解调器程序,就要用到在异步串行口上进行读写字符。 这里又强行作了char 转换,以保证放在AH 寄存器中的任何值, 都不会让任何调用例 程乱了套。 /*向串口写字符*/ #include <dos.h> int put_async(char ch) { bdos(0x4,ch,0); } /*从串口读字符*/ #include <dos.h> int get_async() { return((char)bdos(0x3,0,0)); } 12 / 26

  13. 课件制作:刘达明 023-66834110 (第九章 编程技巧) 第三节与汇编语言的接口 在C语言编程中,需要使用汇编语言编写例程大概有三个方面的原因: ⑴为了提高速度和效率。 ⑵为了实现某些C语言中不具备、但为不同的机器所特有的功能。 ⑶为了利用通用的汇编语言例程。 把汇编程序模块和用户的C程序结合起来,主要有两种方法:第一种,单独 编写汇编例程,然后再将它同自己的程序连接起来。第二种,使用大多数C编译 器所具有的内部汇编程序功能。 有的C编译系统内部汇编使用#asm开头,使用#endasm结束,中间全部是汇编 语句。而TurboC则不同,每行汇编语句都要以asm开头。如:两数相乘的函数。 int mul(int a,int b) { #asm mov ax,word ptr 8[bp] imul ax,word ptr 10[bp] #endasm } int mul(int a,int b) { asm mov ax,word ptr 8[bp] asm imul ax,word ptr 10[bp] } 13 / 26

  14. 课件制作:刘达明 023-66834110 (第九章 编程技巧) 第四节程序调试问题 一、程序易出错问题 1、忘记定义变量。 如:main() {x=6;y=8;z=x+y;} 在使用变量之前,必须加:int x,y,z; 定义变量。 2、输入输出数据的类型与所用格式说明符不一致。 如:int a=3; float b=4.5; printf(“%f,%d\n”,a,b);结果可能不是所需。 3、未注意int型数据的取值范围。 如:int num; num=89101; printf(“%d\n”,num); 造成num超界益出。 4、输入变量时忘记使用地址符。 如:scanf(“%d %d”,a,b); 应改为:scanf(“%d %d”,&a,&b); 5、输入时数据的组织与要求不符。 如:scanf(“%d %d”,&a,&b);输入若按:3,4(Enter)则是错的,两数之间应 为空格,而不是逗号。 再如:scanf(“Input a & b: %d,%d”,&a,&b);想在屏幕上提示Input a & b: 再输入则是不正确的。可改用printf(“Input a&b:”); scanf(“%d,%d”,&a,&b); 这时屏幕显示:Intput a&b: 后再输入:3,4(Enter) 14 / 26

  15. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 6、误把“=”作为“等于”比较符。 如:if(a=b) 应改为:if(a==b) 应该用“==”作为“等于”比较符。前则为 赋值操作。 7、语句后面漏分号。 如:a=4 b=5 应为:a=4; b=5; 复合语句的最后一句也应该有分号。 如:{t=a; a=b; b=t } 应改为:{t=a; a=b; b=t; } 8、在不该加分号的地方加分号。 如:if(a>b); printf(“a is larger than b.\n”); 再如:for(i=0;i<10;i++);{scanf(“%d”,&x);printf(“%d\n”,x*x);}它不能 输入10个数据,而只能输入一个数据。 9、对应该有花括弧的复合语句,忘记加花括弧。 如:sum=0; i=1; while(i<=100) sum=sum+i; i++; 结果是在循环中i的值 永远不变,循环是死循环。循环应改为:while(i<100){sum=sum+i;i++;} 10、括号不配对。 如:while((c=getchar()!=‘#’) putchar(c); while语句后面少了右括号。 15 / 26

  16. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 11、在用标识符时,忘记了大写字母和小写字母的区别。 如:int a,b,c; a=2; b=3; C=A+B; 大写与小写字母在C语言中为两个不同 的标识符。 12、引用数组元素时误用了圆括号。 如:int i,a(10); for(i=0;i<10;i++) scanf(“%d”,&a(i)); 应改为: int i,a[10]; for(i=0;i<10;i++) scanf(“%d”,&a[i]); 13、在定义元素时,将定义的“元素个数”误认为是“可使用的最大下标值”。 如:int a[10]={1,2,3,4,5,6,7,8,9,10},i; for(i=1;i<=10;i++) printf(“%d\t”,a[i]); 数组下标是从0开始,到“元素个数-1”为止的。for应为:for(i=0;i<10;i++) 14、对二维数组或多维数组的定义和引用的方法不对。 如:int a[5,4]; … printf(“%d”,a[1,2]); …。数组的每一维下标均应该 用一对方括号括起来。而a[1,2]相当于a[2]为数组第4行的首地址。 应改为:int a[5][4]; printf(“%d”,a[1][2]); 15、误以为数组名代表数组中全部元素。 如:int a[4]={1,3,5,7}; printf(“%d,%d,%d,%d\n”,a); 16 / 26

  17. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 16、混淆字符数组与字符指针的区别。 如:char s1[20],*s2; s1=“Computer and C”; s2=“C and Computer”; printf(“s1=%s,s2=%s\n”,s1,s2); 编译出错。s1是数组名,代表数组首地址,是 常量,不能再赋值。而s2是指向字符数据的指针变量,对s2可以赋值。s1的赋值 应改为:strcpy(s1,”Computer and C); 或 char s1[20]=“Computer and C”; 17、在引用指针变量之前没有对它赋予确定的值。 如:char *p,c[20]; scanf(“%s”,p); 应在这两句间加上:p=c;语句。 18、switch语句的各分之中漏写break语句。 switch(score) { case 5: printf(“Very good!”); case 4: printf(“Good!”); break; case 3: printf(“Pass!”); case 5: printf(“Fail!”); defult: printf(“data error!”); } 如果score为5时,将打印出: Very good! Good! 而我们希望只有 Very good!输出。程序应改为:在 每个case标号后执行完对应的语句 之后要加上break;语句。case后是一 个常量值,可用整型数据常量或字 符数据常量,只起语句标号作用。 17 / 26

  18. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 19、混淆字符和字符串的表示形式。 如:char sex,a[10]=‘a’; sex=“M”; 应改为:char sex,a[10]=“a”;sex=‘M’; 20、使用自加(++)和自减(--)运算符时出错。 如:int *p,a[4]={1,3,5,7},x; p=a; x=*p++; 作用是将*p(这时应为a[0]) 赋给x,然后p再自加变成指向a[1]元素。但过段时间后,程序员觉得应该为: 将p所指向元素的值乘以p所指向元素的值再赋给x。写成:x=*p++*(*p);其结果 相当于:a[0]*a[1],这不是我们要的结果a[0]*a[0]。应为:x=*p*(*p++); 21、有人习惯用传统的方式对函数形参进行声明,但却把对函数的形参和 函数中的局部变量混在一起定义。 如:max(x,y) 应改为:max(x,y) int x,y,z; int x,y; { { int z; z=x>y?x:y; z=x>y?x:y; return(z); return(z); } } 18 / 26

  19. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 22、所调用的函数在调用语句之后才定义,而又在调用之前未加声明。 如:main() float max(float x,float y) { float x=3.5,y=-7.6,z; {return(x>y?x:y); } z=max(x,y); 必须在main()函数中声明: printf(“%f\n”,z); float max(float,float); } 或在main()函数之前定义max函数。 23、误认为形参值的改变会影响实参的值。 如:main() swap(int x,int y) { int a=3,b=4; { int t; swap(a,b); t=x; x=y; y=t; printf(“%d,%d\n”,a,b); } } 可用指针方式定义swap函数解决传递。 24、函数的实参和形参类型不一致。 如:main() fun(float x,float y) { int a=3,b=4,c; { … } c=fun(a,b); …} 可改为:fun(int x,int y) 或a,b为float。 19 / 26

  20. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 25、不同类型的指针混用。 应改为:main() { int i=3,*p1; float a=1.5,*p2; p1=&i; p2=&a; p2=(float *)p1; printf(“%d,%d\n”,*p1,*p2); } 如:main() { int i=3,*p1; float a=1.5,*p2; p1=&i; p2=&a; p2=p1; printf(“%d,%d\n”,*p1,*p2); } 26、没有注意函数参数的求值顺序。 如:i=3; printf(“%d,%d,%d\n”,i,++i,++i); 有些系统输出:3,4,5。而TurboC等有 些系统则输出:5,5,4。即求函数参数表达式的值的顺序有:从左向右的,也有 从右向左的,注意区分顺序。一般改用先赋值,再带入不含++或--的变量。 27、混淆数组名与指针变量的区别。 应改为:int i,a[5],*p; for(p=a,i=0;i<5;i++) scanf(%d”,p++); 如:int i,a[5]; for(i=0;i<5;i++) scanf(%d”,a++); 20 / 26

  21. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 28、混淆结构体类型与结构体变量的区别,对一个结构体类型赋值。 应改为:struct worker { int num; char name[20]; }worker1; worker1.num=1001; strcpy(worker1.name,”Li”); 如:struct worker { int num; char name[20]; }; worker.num=1001; strcpy(worker.name,”Li”); 29、使用文件时忘记打开,或打开方式与使用情况不匹配。 如:对文件的读写,用只读方式打开,却企图向该文件输出数据。此外,有 的程序常忘记关闭文件,虽然系统会自动关闭所用文件,但可能会丢失数据。 其它还有指针引用错误造成野指针、函数重名、恶性语法错误、边界错误、 函数说明的疏漏、调用参数错误、scanf()与gets()函数的区别等等。 以上错误在多练习C语言编程后可以克服,并且也容易检查。在深入使用C语 言后,还会出现其它一些更深入、更隐蔽的错误。 21 / 26

  22. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 二、程序出错的三种情况 1、语法错误 程序违背C语法规定引起的错误。编译程序一般能发现错误,根据“出错信息” 可以很快发现并及时纠正。 2、逻辑错误 程序没有语法错误,但执行结果与原意不符。如:sum=0; i=1; while(i<=100) sum=sum+i; i++; 最后两句应用一对花括号括起来。这种错误比较难查,要求程 序员有较丰富的经验。 3、运行错误 程序既无语法错误,也无逻辑错误,但在运行时出现错误,甚至停止运行。 如:int a,b,c; scanf(“%d %d”,&a,&b); c=b/a; printf(“c=%d\n”,c); 如果输入a的值 为0的话,就会出错。因此程序应经受各种数据的“考验”,应具有“健壮性”。 写完一个程序只能说完成任务的一半(甚至不到一半)。调试程序往往比写程序 更难,更需要精力、时间和经验。程序员需要通过大量的实践来掌握调试程序 的方法和技术。 22 / 26

  23. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 三、程序调试 所谓程序调试是指对程序的查错和排错。调试程序一般经过以下几个步骤。 1、人工检查,即静态检查 程序写好后,首先应进行人工检查。作为一个程序员应当养成严谨的科学作 风,每一步都要严格把关,不要把问题留给后面的工序。为了更有效地进行人 工检查,编程人员应力求做到以下几点: ⑴应当采用结构化程序方法编程,以增加可读性。 ⑵尽量多加注释,以帮助理解每段程序的作用。 ⑶在编写复杂程序时,不要将全部语句都写在main函数中,而要多利用函数, 用一个函数来实现一个单独的功能。各函数之间除用参数传递数据这一渠道外, 数据间尽量少出现偶合关系,便于分别检查和处理。 ⑷编写程序时,要注意编程风格。好的编程风格也便于查错。 2、上机调试 通过上机发现错误称为动态检查。主要根据编译时给出的错误信息来纠错。 应当注意:有时提示的出错行并不是真正的出错行,应往上查找。可能改了前 面的某一个错误后,后面的大片错误就消失。要分析,找出真正的错误行。 23 / 26

  24. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 3、检查运行结果的正确性 在改正“错误”(error)和“警告”(warning)后,程序经过连接(link)就得到可执行的 目标程序。运行程序,输入程序所需要的数据,就可得到运行结果。还应当分 析运行结果,检查它是否符合要求。在验证程序时,应精心选择典型、苛刻而 带有刁难性的几组数据进行输入,看它是否能够得出满足要求的结果。 4、检查运行结果错误的方法 如果运行结果不对,大多属于逻辑错误。对这类错误往往需要仔细检查和分 析才能发现。可以采用以下方法: ⑴将程序与算法仔细对照。算法正确,就是程序错误,仔细对照就很快发现 错误。算法不对,就修正算法,再改写程序。 ⑵如果实在找不到错误,就采取“分段检查”的方法。在程序不同位置设计个 printf()函数输出相关信息或变量值,逐段往下查。直到找到在某一段中数据 不对为止。 ⑶也可用“条件编译”命令来处理调试用的printf()函数语句,用完不必删除。 ⑷如果程序没有错误,就要检查算法了。修改算法后再修定程序。 ⑸有的系统还提供了debug(调试)工具,可跟踪程序运行并给出相应信息。 24 / 26

  25. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 三、C的存储方式 在使用8086系列处理机上运行C程序时,C编译系统都提供了6种存储方式: 小方式、一般方式、中等方式、压缩方式、大方式、特大方式。8086用分段存 储结构,有四个段:代码段、数据段、堆栈段和附加段。一个段在RAM中占 64K,段的起点正好是16字节的偶数倍。 1、小方式 要求程序、数据和栈都必须在同一64K段内。用它编译的程序,目标码最少, 执行起来最快。可用DOS的EXE2BIN命令转换成.COM文件。 2、一般方式 这种方式是缺省方式,用得最多。程序代码单独占用64K,数据码占用另外64 K。按这种方式编译的程序最大为128K。 3、中等方式 机器码超过了一般方式下的一个段的限制的大程序,就要用中等方式编译。 这种方式适合只使用少量数据的大程序。 4、压缩方式 压缩方式与中等方式相反,适合使用大量数据,但本身程序不大的情况。 25 / 26

  26. 课件制作:刘达明 023-66834110 第四节程序调试问题 (第九章 编程技巧) 5、大方式 大方式允许程序码和数据都使用多个段。但最大数据项(如数组)单项最多只能 占用64K当程序和数据都很大时,要用大方式。 6、特大方式 特大方式与大方式有一点不同,数据单独可占64K以上的内存。 方式的选择: 除非有特殊理由,一般情况下都应使用一般方式。 当程序很大,但数据不多时,可选用中等方式。 而程序不大,数据却很多时,应选用压缩方式。 如果程序和数据都不小,则选用大方式。 当某些数据项目单项大于64K时,用特大方式。 在8086上运行C程序,还可以使用far、near、huge三个说明符来解决存储方式 混用的问题,它们只能用于指针和函数。详细信息参见有关资料。 26 / 26

More Related