《嵌入式系统程序设计.ppt》由会员分享,可在线阅读,更多相关《嵌入式系统程序设计.ppt(282页珍藏版)》请在三一办公上搜索。
1、嵌入式系统程序设计,大连理工大学软件学院嵌入式系统工程系赖晓晨,C/C+语言摘要,C语言的历史和特点预处理程序位运算函数指针C程序的移植,一、C语言的历史和特点,C语言的历史C语言的特点,C语言的优良特性,结构化语言,代码数据分离包含指针特性,允许对地址操作语法简洁紧凑,但功能强大编程方便,运行速度快支持分离编译,C语言的缺点,封装性不如C+,数据安全性上有缺陷 类型检查机制相对薄弱 指针的操作带来许多不安全因素 比其他高级语言较难掌握,二、预处理程序,C89规定的预处理指令有以下几条#if#ifdef#ifndef#else#elif#endif,#define#undef#line#err
2、or#pragma#include,预处理语句的书写规则,作用:对源程序编译之前做一些处理,生成扩展C源程序格式:“#”开头占单独书写行语句尾不加分号,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),如#define YES 1#define NO 0#define PI 3.1415926#define OUT printf(“Hello,World”);,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指
3、定标识符(宏名)代替字符序列(宏体),宏体可缺省,表示宏名定义过,如#define YES 1#define NO 0#define PI 3.1415926#define OUT printf(“Hello,World”);,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),定义位置:任意(一般在函数外面)作用域:
4、从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,如 if(x=YES)printf(“correct!n”);else if(x=NO)printf(“error!n”);展开后:if(x=1)printf(“correct!n”);else if(x=0)printf(“error!n”);,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),宏展开:预编译时,用宏体替换宏名-不作语法检查,定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,1.
5、宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),宏展开:预编译时,用宏体替换宏名-不作语法检查,定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,引号中的内容与宏名相同不置换,例#define ID 1 语句printf(ID);会输出ID,而非1,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),宏展开:预编译时,用宏体替换宏名-不作语法检查,定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef
6、可终止宏名作用域 格式:#undef 宏名,宏定义可嵌套,不能递归,例#define ID ID+1(),引号中的内容与宏名相同不置换,例#define DIS1 10#define DIS2 DIS1+10 var=DIS2*2;宏展开:var=10+10*2;,1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),宏展开:预编译时,用宏体替换宏名-不作语法检查,定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,宏定义可嵌套,不能递归,引号中的内容与宏名相同不置换,宏定义中
7、使用必要的括号(),1.宏定义不带参数宏定义一般形式:#define 宏名 宏体功能:用指定标识符(宏名)代替字符序列(宏体),宏展开:预编译时,用宏体替换宏名-不作语法检查,定义位置:任意(一般在函数外面)作用域:从定义命令到文件结束#undef可终止宏名作用域 格式:#undef 宏名,宏定义可嵌套,不能递归,引号中的内容与宏名相同不置换,宏定义中使用必要的括号(),宏体可以省略,表示宏名已被定义过,/*ch3_1.c*/#include#define Aint main()#ifdef Aprintf(A has been definedn);#elseprintf(A has not
8、been definedn);#endifreturn 0;输出为:“A has been defined”,即使把宏定义改为:#define A0输出仍旧为:“A has been defined”。,带参数宏定义一般形式:#define 宏名(参数表)宏体,例#define T(m,n)m*n相当于定义了不带参宏T,其宏体为“(m,n)m*n”,宏展开:形参用实参换,其它字符保留,例#define T(m,n)m*n.area=T(3,2);宏展开:area=3*2;,不能加空格,带参数宏定义一般形式:#define 宏名(参数表)宏体,宏展开:形参用实参换,其它字符保留宏体及各形参外一般
9、应加括号(),例#define T(m,n)m*n.area=T(3,2);宏展开:area=3*2;,例#define CUBE(x)x*x*x a=4;b=6;z=CUBE(a+b);宏展开:z=a+b*a+b*a+b;一般写成:#define CUBE(x)(x)*(x)*(x)宏展开:z=(a+b)*(a+b)*(a+b);,带参的宏与函数区别,Embest开发环境中的宏定义,44b.h有44B0X中各个特殊功能寄存器的宏定义,#define rBWSCON(*(volatile unsigned*)0 x1c80000)#define rBANKCON0(*(volatile uns
10、igned*)0 x1c80004)#define rBANKCON1(*(volatile unsigned*)0 x1c80008)#define rBANKCON2(*(volatile unsigned*)0 x1c8000c)#define rBANKCON3(*(volatile unsigned*)0 x1c80010)#define rBANKCON4(*(volatile unsigned*)0 x1c80014)#define rBANKCON5(*(volatile unsigned*)0 x1c80018),预定义宏,C89规范了五个固有的预定义宏,分别为:_LINE_
11、:行号_FILE_:文件名_DATE_:日期_TIME_:时间_STDC_:1标准C编译器 0非标准C编译器,预定义宏,#include int main()printf(The current file is:%sn,_FILE_);printf(The current line number is:%dn,_LINE_);printf(today is:%sn,_DATE_);printf(the time is:%sn,_TIME_);if(_STDC_=1)printf(this is a standard compilern);else if(_STDC_=0)printf(this
12、 is not a standard compilern);,/exp/pre/macro.c,功能:对源程序中的一部分内容只有满足某种条件的情况下才进行编译。,2.条件编译,#ifdef 标识符程序段1#else程序段2#endif,形式1:,当标识符已经被定义过(使用#define),则对程序段1进行编译,否则编译程序段2。其中#else部分可以省略。,形式1(续):,#define IBM-PC 0/*或#define IBM-PC*/。#ifdef IBM-PC#define INT 16#else#define INT 32#endif,可以用来提高程序的可移植性,形式1(续):,#
13、define DEBUG.#ifdef DEBUGprintf(“x=%d,y=%d”,x,y);#endif,可以用来调试程序,调试结束后只需将define行删掉即可,#ifndef 标识符程序段1#else程序段2#endif,形式2:,当标识符未被定义过,则对程序段1进行编译,否则编译程序段2。(与形式1正相反),#if 表达式程序段1#else程序段2#endif,形式3:,当表达式为真时,则对程序段1进行编译,否则编译程序段2。,#include#define CAP 1int main()char string20=I love China;char c;int i=0;c=str
14、ingi+;while(c!=0)#if CAPif(c=a,形式3:(续),3.文件包含功能:一个源文件可将另一个源文件的内容全部包含进来一般形式:#include“文件名”或#include,直接按标准目录搜索“”先在当前目录搜索,再搜索标准目录可指定路径,3.文件包含功能:一个源文件可将另一个源文件的内容全部包含进来一般形式:#include“filename”或#include,处理过程:预处理时,用被包含文件的内容取代该预处理命令,再把“包含”后的文件作为一个源文件编译,被包含文件内容源文件(*.c)头文件(*.h),宏定义数据结构定义函数说明等,实际上文件名也可以是C源文件,不过这
15、不是良好的编程风格,【例3-5】文件包含一下程序包含一个完整的模块(function.c、function.h、test.c),/*funtion.h*/#ifndef _FUNCTION#define _FUNCTIONvoid f();#endif/*function.c*/#include void f()printf(a example of#include.n);,/*test.c*/#include#include function.hint main()f();return 0;,头文件格式,4.其他预处理指令,#error指令强制编译器停止编译,主要用于程序调试。#error指
16、令的一般形式为:编译到#error时,会显示相应字符串,#error error-message,#error举例,#define CON10#define CON21#define CON3-1int main()#ifCON1#ifCON2#error run to position1#else#error run to position2#endif#else#ifCON3#error run to position3#else#error run to position4#endif#endif,明确程序编译位置,三、位运算,1.位与&,运算规则:两个位都为1,结果为1,否则为0例如:
17、9&0 x0c结果为8,2.位或|,运算规则:两个位都为0,结果为0,否则为1例如:9|0 x0c结果为0 x0d,3.按位取反,运算规则:1变0,0变1用途:使某位取反,3.按位取反(续),例:使某数最低位为0,int a;a=a&0 xfffffffe,3.按位取反(续),例:使某数最低位为0,思考:有没有隐含的问题,int a;a=a&0 xfffffffe,3.按位取反(续),例:使某数最低位为0,/*16位机*/int a;a=a&0 xffffe/*32位机*/a=a&0 xfffffffe,可移植性变差,3.按位取反(续),例:使某数最低位为0,a=a&1/*1能自动适应16位机
18、以及32位机*/,解决办法,4.按位异或,运算规则:判断两位是否相同,同则为0,否则为1用途:使特定位翻转 交换两个值,不用临时变量,4.按位异或(续),不通过中间变量交换两个变量的值/*ch3_7.c*/#include int main()int a=21;int b=43;a=ab;b=ba;a=ab;printf(a=%d,b=%dn,a,b);return 0;,5.左移位,运算规则:将一个数的全部二进制位左移若干位,移出位舍弃,右侧补0用途:移出位没有1时,相当于乘法运算,6.右移位,运算规则:将一个数的全部二进制位右移若干位,移出位舍弃,左侧可能补0或者补1,视计算机系统不同而不
19、同。符号问题:无符号数右移,左侧补0有符号数视计算机系统而定:逻辑右移、算术右移,例:循环右移n位,例:循环右移n位(续),#include int main()unsigned a,b,c,n;scanf(a=%x,n=%d,输入:a=0 x12345678,n=8,显示:a=12345678,c=78123456,四、函数指针,每一个函数模块都有一个首地址,称为函数的入口地址。指向函数的指针称为函数指针,保存的是函数的入口地址(首地址)函数调用:找到函数入口地址;传递参数,1.函数指针的定义方法,格式:int(*ptr1)(int);定义了函数指针ptr1,此指针只能保存具有一个整型参数的
20、整型函数的首地址。注意:int*ptr1(int);这是一条函数声明语句,表示一个带有整数参数的函数返回,返回一个整型指针。int(*ptr1)(int);这个是函数指针,例:,以0.1为步长,计算特定范围内的三角函数之和。,sin(0.1)+sin(0.2)+sin(1.0)cos(0.5)+cos(0.6)+cos(3.0),#include#include double triangle(double(*func)(double),double begin,double end)double step,sum=0.0;for(step=begin;stepend;step+=0.1)su
21、m+=func(step);return sum;int main()double result;result=triangle(sin,0.1,1.0);printf(the sum of sin from 0.1 to 1.0 is%fn,result);result=triangle(cos,0.5,3.0);printf(the sum of cos from 0.5 to 3.0 is%fn,result);return 0;,#include#include double triangle(double(*func)(double),double begin,double end)
22、double step,sum=0.0;for(step=begin;stepend;step+=0.1)sum+=func(step);return sum;int main()double result;result=triangle(sin,0.1,1.0);printf(the sum of sin from 0.1 to 1.0 is%fn,result);result=triangle(cos,0.5,3.0);printf(the sum of cos from 0.5 to 3.0 is%fn,result);return 0;,2.用typedef来简化函数指针,对参数较多的
23、函数类型,定义相应的函数指针比较烦琐,可用typedef关键字简化例如typedef double(*FUN)(double a,double b);,用typedef来简化函数指针(续),格式:int(*ptr1)(int);定义函数指针的别名方法:定义了函数指针ptr1,此指针只能保存具有一个整型参数的整型函数的首地址。,double(*fun1)(double a,double b);typedef double(*FUN)(double a,double b);FUN f1;FUN f2;,用typedef来简化函数指针(续),格式:int(*ptr1)(int);定义函数指针的别名方
24、法:(也可以这样)定义了函数指针ptr1,此指针只能保存具有一个整型参数的整型函数的首地址。,double(*fun1)(double a,double b);typedef double(FUN)(double a,double b);FUN*f1;FUN*f2;,3函数指针数组,声明方式定义了表示定义了一个长度为4的函数指针数组,同时做了初始化。,double(*fp4)(double,double)=f1,f2,f3,f4,4函数指针应用,在嵌入式操作系统中,经常用函数指针来完成任务的调度。例如uC/OS中,任务创建的函数原型为第一个参数为函数指针,INT8U OSTaskCreate(
25、void(*task)(void*pd),void*pdata,OS_STK*ptos,INT8U prio);,五、C程序的移植,为一种机器写的程序,经常需要在其他硬件、操作系统的平台上运行,往往需要对此程序进行一些改动,这个过程叫做程序的移植方便移植的程序称为可移植程序程序不可移植,主要是因为有太多硬件相关的代码,1、避免使用“魔数”,“魔数”(magic number):依赖于系统或处理器的数字。例如表示硬盘缓冲区的大小、屏幕和键盘的特定尺寸等数字。“魔数”的出现使系统可移植性变差。下列代码本质上是不可移植的,fread(buf,256,1,fp);/缓冲区为256B,使用#define
26、替换“魔数”,程序中要尽量避免“魔数”的硬编码,可以用宏来取代魔数,使可读性增强,而且移植程序时只要修改宏一处即可,#define BUFFER_SIZE 256fread(buf,BUFFER_SIZE,1,fp);,例,一个图形处理程序中,需要不同的颜色执行不同操作,例,/*恶劣的例子*/void ShowColor(int color)if(color=0)sub_red();else if(color=1)sub_blue();else if(color=2)sub_green();return;,例(改进后的程序),/*然后在源文件中直接使用这些宏来判断*/#include colo
27、r.hvoid ShowColor(int color)if(color=RED)sub_red();else if(color=BLUE)sub_blue();else if(color=GREEN)sub_green();return;,/*color.h*/#define RED 0#define BLUE 1#define GREEN 2,2、程序分层,不同的操作系统为应用程序提供了不同的支持,例如windows2000应用程序可以有多线程特性,但是windows3.2不可以,应用程序对系统有依赖型。这种依赖性没有具体通用的解决方法,但是可以通过程序分层来解决,把系统相关的代码放到一起
28、。,3、注意数据类型的长度,16位机中整型数据是2字节,32位机为4字节,这会导致程序的不兼容。可以利用宏来重定义数据类型,#define int16 int/16位机int16 a;,#define int16 short int/32位机int16 a;,注意数据类型的长度(续),16位机中整型数据是2字节,32位机为4字节,这会导致程序的不兼容。使用可适应任何情况的编码方式,例如把一个整型数据写入磁盘:,fwrite(/好,4、对齐问题,某些计算机允许数据边界地址不对齐,把这样的代码移植到ARM上时要小心。ARMv5TE前的处理器都不支持地址不对齐的指针,5、大小端问题,如果两台计算机的
29、大小端定义不一致,那么代码移植时要做转换,6、枚举类型,enum是可移植的,但是不同编译器中对enum分配的字节数可能不同。不能在不同的编译器之间对代码进行交叉连接,7、减少内嵌汇编,C语言的内嵌汇编由C编译器来负责编译,而不使用armasm或gas。内嵌汇编可以提高编程效率,但是会影响到程序的可移植性。,C语言与C+语言的区别,变量定义位置结构体变量数据类型输入输出动态内存分配其他区别,1.变量定义位置,C89要求所有变量都必须定义在块的最前部C+没有这个要求,可以在程序任意位置定义新的变量,2.结构体变量,在C+中,struct结构体支持成员函数的定义,C中不行。如果在C的struct中定
30、义函数,编译时会显示一个“field function name declared as function”错误,2.结构体变量(续),/*ch3_10.c*/struct Aint a;int b();int main()struct A c;c.a=2;return 0;编译错误:“ch3_10.c:5:error:field b declared as a function”C+标准可以通过编译,2.结构体变量(续),在C语言中,声明一个结构体类型A之后,使用下面的语句来定义结构体变量a:struct A a;而C+语言中可以省略struct,3.数据类型,C+中有bool(或boole
31、an类型);C中没有这样的bool类型,均为数值类型!C编译器不能通过编译,C+编译器可以,bool a;a=1;,4.输入输出,C中使用printf、scanf输入输出使用时不用包含任何头文件但如果使用g+编译时必须加上stdio.h头文件,int a;scanf(%d,A.scanf()函数,功能:从键盘读入指定格式的数据格式:scanf(控制字符串,输入项列表);注意:scanf中各变量一定是表示地址的标识符(加&),控制字符串,控制字符串有两部分组成:格式说明形式:%普通字符空格可打印字符,格式说明,各格式字符及其意义:(详见C教程)d:输入一个十进制整数o:输入一个八进制整数x:输入
32、一个十六进制整数f:输入一个小数形式的浮点数e:输入一个指数形式的浮点数c:输入一个字符s:输入一个字符串,空格,在多个输入时,一般用空格或回车作为分隔符若以空格作为分隔符,当输入中包含字符类型时,可能产生非预期的结果,scanf(%d%c,输入:45 q输出:45 空格,空格(续),如下语句会有正确输出此处%d后的空格,就可以跳过字符q前的所有空格,scanf(%d%c,输入:45 q输出:45 q,可打印字符,看一个例子输入为:1,2,q可以得到 a=1,b=2,ch=q输入为:1 2 q除a的值为1外,对b与ch的赋值失败,scanf(%d,%d,%c,B.printf()函数,功能:从
33、缺省输出设备(一般为显示器)输出规定格式的字符串格式:printf(控制字符串,输入项列表);,控制字符串,控制字符串有两部分组成:格式说明形式:%普通字符空格可打印字符,格式说明,各格式字符及其意义:(详见C教程)c:按字符型输出o:按八进制输出d:按十进制输出x:按十六进制输出u:按无符号整数输出f:按浮点型小数输出g:按e和f格式中较短的一种输出e:按科学计数法输出,普通字符,普通字符:可打印字符主要是说明字符,按原样输出,支持汉字输出转义字符(例)不能直接打印,控制产生特殊的输出效果,普通字符(续),转义字符示例,i=789,n=123,a=92.34567,且i为整型,n为长整型。p
34、rintf(%4dt%7.4fnt%lun,i,a,n);输出为:78992.3457 123,C语言输入输出总结,输入输出可能是C和C+的最明显的区别C中用scanf(),printf()来完成输入输出操作C+中全局对象cin、cout来输入输出,比C更方便,而且类型检查机制更加完善,C+中的使用方式new申请delete释放C中的使用方式malloc()申请free()释放,5.动态内存分配,函数原型:void*malloc(long size);作用:在对内存中分配size各字节,并返回了指向这块内存首地址的指针如果分配失败,返回NULL返回指针为void*型的,要强制转换,A.mall
35、oc()函数,函数原型:void free(void*FirstByte);作用:将之前用malloc申请的空间归还操作系统否则就导致内存泄漏编译器不会发现内存泄漏这样的错误,B.free()函数,C.函数的用法,/*例 3-11*/#include#include int main()int*p;if(p=(int*)malloc(sizeof(int)=NULL)printf(动态内存分配失败n);exit(1);,C.函数的用法(续),*p=100;printf(%dn,*p);free(p);p=NULL;return 0;,头文件:malloc和free被头文件stdlib.h包含C
36、+中new和delete为关键字,故无需头文件包含使用:int*p=(int*)malloc(sizeof(int);int*p=new int;,与C+的几点区别,6.其他区别,常量表示方法不同C语言不支持引用的概念,而C+支持注释不同,C89不支持单行注释(+i)+在C中不合法(a=3)=4在C中不合法不能在for循环头部定义变量,GNU C扩展,64位整型数据类型内联函数attribute关键字单行注释,switch case 简写预定义宏结构体和初始化长度为0的数组,64位整型数据类型,GNU CC(GNU Compiler Collection)定义了64为整型关键字long lon
37、g,可以直接操作64位数据,long long int a=0 x123;,expgnu_clong.c,内联函数,减少函数调用开销增加类型检查,比宏使用更安全inline是建议而非命令,attribute关键字,关键字attribute通过向GCC指明有关代码的更多信息来帮助代码优化工作进行的更好。,attribute关键字(续),下面代码通过使用_attribute_,当t未被使用时编译器不会警告,int main()float t _attribute_(unused);return 0;,例3-12,#include void quit()_attribute_(noreturn);v
38、oid quit()exit(1);int test(int n)if(n 0)quit();elsereturn 0;,int main()test(1);return 0;,函数不返回,【例3-13】,/*ch3_13.c*/#include struct s int a2 _attribute_(aligned(8);struct tchar a;int b2 _attribute_(packed);,int main()int y _attribute_(aligned(16);y=1;return 0;,以8字节对齐,单行注释,int main()/long long t1;int
39、t4=3;return 0;,expgnu_cnote.c,switch case 简写,/*ch3_14.c*/#include int main()int t=2;/*传统方式*/switch(t)case 0:case 1:case 2:printf(012n);break;case 3:case 4:case 5:printf(345n);,switch case 简写,/*GCC扩展方式*/switch(t)case 0.2:printf(012n);break;case 3.5:printf(345n);return 0;,expgnu_ccase.c,gcc允许这样写。,预定义宏
40、,/*ch3_15.c*/#include void f(void)printf(This is function%s n,_FUNCTION_);int main()printf(This is function%s n,_FUNCTION_);f();return 0;,expgnu_cfunc.c,结构体初始化,struct file_operations ext2_file_operations=llseek:generic_file_llseek,read:generic_file_read,write:generic_file_write,ioctl:ext2_ioctl,mmap
41、:generic_file_mmap,open:generic_file_open,release:ext2_release_file,fsync:ext2_sync_file,;,expgnu_cfunc.c,结构体初始化,struct file_operations driver_fops=owner:THIS_MODULE,read:driver_read,open:driver_open,release:driver_release,;其他的值被初始化为0.,expgnu_cfunc.c,长度为0的数组,GNU还允许结构体的末尾成员是长度为0的数组,错误处理机制,C语言错误处理机制系统
42、日志文件,一、C语言的错误处理机制,C标准中有几个错误处理机制 预定义宏预定义全局变量若干库函数,1.assert宏,assert宏定义在assert.h中语法格式:作用:计算表达式expression的值,如果为0,那么首先向stderr打印一条出错信息,然后调用abort()函数终止程序运行。,assert(expression),【例3-16】,/*ch3_16.c*/#include#include#includeint main()char*p;p=getenv(HOME);assert(p);printf(HOME=%sn,p);p=getenv(NOTEXIST);assert(
43、p);printf(NOTEXIST=%sn,p);return 0;,/exp/error/badptr.c,assert的缺点,assert的调用会影响程序的运行效率,有时希望程序中的assert不会起作用,这可以通过在程序前部包含assert.h之前定义一个 NDEBUG来实现。,【例3-17】,/*ch3_17.c*/#define NDEBUG#include#include#includeint main()char*p;p=getenv(HOME);assert(p);printf(HOME=%sn,p);p=getenv(NOTEXIST);assert(p);printf(N
44、OTEXIST=%sn,p);return 0;,/exp/error/badptr2.c,assert使用注意事项,当使用#define NDEBUG来禁用assert宏时,注意assert宏中隐含的某些操作不能完成,而这些代码可能对后面的操作有关键作用。,定义NDEBUG,p赋值行未执行,使用p时出错。,assert使用注意事项举例,#define NDEBUGassert(p=malloc(sizeof(char)*100);free(p);,#define NDEBUGp=malloc(sizeof(char)*100);assert(p);free(p);,应该按照这种方式来编写,P
45、是野指针,assert使用讨论,assert宏只提供了一种粗糙的终止程序运行的方式,这种方法并不理想。理想的方式是“适当的降级”,在不同层次的出错处理都失败了,别无选择的情况下才终止程序的运行。在不得不警告或者提示用户之前能成功的处理的出错越多,程序就越健壮。,2.位置指示宏,C标准定义了两个宏:_LINE_FILE_把这两个宏用于可以更精确的定位程序的出错位置。,【例3-18】,#include#include int show_environment(char*e,int num,char*name)char*p=getenv(e);if(p!=NULL)printf(the enviro
46、nment is:%sn,p);return 0;elseprintf(error occured at%d of%s.n,num,name);return 1;,/exp/error/filefcn.c,【例3-18】(续),int main()char*e1=HOME;char*e2=NOTEXIST;int res;res=show_environment(e1,_LINE_,_FILE_);if(res)return 1;res=show_environment(e2,_LINE_,_FILE_);if(res)return 1;return 0;,/exp/error/filefcn
47、.c,3.标准库函数,本节的“标准库”是指任何支持ANSI/ISO C标准的C环境的一部分变量、宏、函数。本节介绍5个函数和一个变量。stdlib.h:void abort(void);void exit(int status);int atexit(void(*fcn)(void);stdio.hvoid perror(const char*s);string.hchar*strerror(int errnum);errno.hint errno;,3.标准库函数(续),C语言还提供了一些标准库函数和全局变量用来支持错误处理,具体包含5个函数,以及一个全局变量,任何ANSI/ISO C标准的
48、实现都包含这些特性,A.errno,Linux系统调用和许多库函数在出错时都要把全局变量errno设置为一个非0值,唯一对应一种出错的情况。在希望通过访问errno以确定错误类型前,要人为的把errno设置为0,因为没有任何一个函数可以把errno设置为0。errno定义在头文件stdlib.h中,【例3-19】,/*ch3_19.c*/#include#include#include#include int main(void)FILE*fp;errno=0;fp=fopen(notexist,r);if(errno)printf(Open file failed.n);elseprintf
49、(Open file successfully.n);,/exp/error/errno.c,检测errno值,【例3-19】(续),printf(try again.n);errno=0;fp=fopen(notexist,w);if(errno)printf(Open file failed.n);elseprintf(Open file successfully.n);return 0;,检测errno值,/exp/error/errno.c,清0,变量errno的常见值,变量errno的常见值(续),变量errno的常见值(续),变量errno的常见值(续),B.abort函数,abo
50、rt函数的功能是立刻终止程序运行,并且不会执行由atexit()登记过的函数原型:void abort(void);,【例3-20】,/*ch3_20.c*/#include#include int main(void)printf(Hello everybody.n);abort();printf(you can not get here.n);return 0;,/exp/error/boom.c,C.exit()函数,exit()函数于abort()的区别在于终止程序前会执行由atexit()登记的函数函数原型:#includevoid exit(int status);exit的类型是