《嵌入式LinuxC语言开发》第3章嵌入式Linux C高级用法

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

123第3章嵌入式LinuxC语言高级用法在本章中,读者将会学习嵌入式LinuxC语言的高级用法,这些在使用嵌入式LinuxC开发的应用程序中是比较常见的。另外,本章也会讲解一些有关嵌入式LinuxC程序可移植性问题、C语言与汇编语言之间的混合编程。本章主要内容:●预处理●C语言中的内存分配●程序的可移植性考虑●C和汇编的接口3.1预处理在本书的第二章中,已介绍过编译过程中的预处理阶段。所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当编译一个程序时,系统将自动调用预处理程序对程序中的“#”号开头的预处理部分进行处理,处理完毕之后可以进入源程序的编译阶段。C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。本节介绍最常用的几种预处理功能。3.1.1预定义在C语言源程序中允许用一个标识符来表示一串符号,称为宏,被定义为宏的标识符称为宏名。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的符号串去替换,这称为宏替换或宏展开。1.预定义符号在C语言中,有一些预处理定义的符号串,它们的值或者是字符串常量,或者是十进制数字常量,它们通常在调试程序时用于输出源程序的各项信息,表3.1归纳了这些预定义符号。表3.1预定义符号表符号示例含义__FILE__/home/david/hello.c正在预编译的源文件名__LINE__5文件当前行的行号__FUNCTION__main当前所在的函数名__DATE__Mar132009预编译文件的日期__TIME__23:04:12预编译文件的时间__STDC__1如果编译器遵循ANSIC,则值为1这些预定义符号通常可以在程序出错处理时应用,下面的程序显示了这些预定义符号的基本124用法。intmain(){printf(Thefileis%s\n,__FILE__);printf(Thelineis%d\n,__LINE__);printf(Thefunctionis%s\n,__FUNCTION__);printf(Thedateis%s\n,__DATE__);printf(Thetimeis%s\n,__TIME__);}要注意的是,这些预定义符号中__LINE__和__STDC__是整数常量的,其他都是字符串常量,该程序的输出结果如下所示:Thefileis/home/david/hello.cThelineis5ThefunctionismainThedateisMar132009Thetimeis23:08:422.宏定义以上是C语言中自带的预定义符号,除此之外,用户自己也可以编写宏定义。宏定义是由源程序中的宏定义#define语句完成的;而宏替换是由预处理程序自动完成的。在C语言中,宏分为带参数和不带参数两种,下面分别讲解这两种宏的定义和使用。(1)无参宏定义无参宏的宏名(也就是标识符)后不带参数,其定义的一般形式为:#define标识符字符串其中的#表示这是一条预处理命令。凡是以#开头的均为预处理命令。define为宏定义命令。标识符为所定义的宏名。字符串可以是常数、表达式、格式串等。在前面介绍过的符号常量的定义就是一种无参宏定义。此外,用户还可对程序中反复使用的表达式进行宏定义,例如:#defineM(y+3)这样就定义了M表达式为(y+3),在此后编写程序时,所有的(y+3)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(y+3)表达式去置换所有的宏名M,然后再进行编译。#defineM(y+3)voidmain(){ints,y;printf(inputanumber:);scanf(%d,&y);s=5*M;printf(s=%d\n,s);125}在上例程序中首先进行宏定义,定义M表达式(y+3),在“s=5*M”中作了宏调用,在预处理时经宏展开后该语句变为:s=5*(y+3)这里要注意的是,在宏定义中表达式(y+3)两边的括号不能少,否则该语句展开后就成为如下所示:s=5*y+3这样显然是错误的,通常把这种现象叫做宏的副作用。对于宏定义还要说明以下几点。宏定义用宏名来表示一串符号,在宏展开时又以该符号串取代宏名,这只是一种简单的替换,符号串中可以包含任何字符,可以是常数,也可以是表达式,预处理程序对它不做任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。宏定义不是声明或语句,在行末不必加分号,如加上分号则连分号也一起置换。宏定义的作用域包括从宏定义命名起到源程序结束,如要终止其作用域可使用#undef命令来取消宏作用域,例如:#definePI3.14159func1(){……}#undefPIfunc2()/*表示PI只在func1()函数中有效,在func2()函数中无效。*/宏名在源程序中若用引号括起来,则预处理程序不对其进行宏替换。#defineOK100main(){printf(OK);}上例中定义宏名OK表示100,但在printf语句中OK被引号括起来,因此不做宏置换。宏定义允许嵌套,在宏定义的符号串中可以使用已经定义的宏名,在宏展开时由预处理程序层层替换。习惯上宏名用大写字母表示,以便于与变量区别,但也允许用小写字母表示。对输出格式做宏定义,可以减少编写麻烦,例如:#definePprintf#defineD%d\n#defineF%f\nvoidmain(){inta=5,c=8,e=11;floatb=3.8,d=9.7,f=21.08;126P(DF,a,b);P(DF,c,d);P(DF,e,f);}(2)带参宏定义C语言允许宏带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中不仅要宏展开,而且要用实参去代换形参。带参宏定义的一般形式为:#define宏名(形参表)字符串在字符串中含有各个形参。带参宏调用的一般形式为:宏名(实参表);例如:#defineM(y)y+3/*宏定义*/若想调用以上宏,可以采用如下方法:K=M(5);/*宏调用*/在宏调用时,用实参5代替宏定义中的形参y,经预处理宏展开后的语句为:K=5+3以下这段程序就是常见的比较两个数大小的宏表示,如下所示:#defineMAX(a,b)(ab)?a:b/*宏定义*/voidmain(){intx=10,y=20,max;max=MAX(x,y);/*宏调用*/printf(max=%d\n,max);}上例程序的第一行进行带参宏定义,用宏名MAX表示条件表达式“(ab)?a:b”,形参a、b均出现在条件表达式中。在程序中“max=MAX(x,y);”为宏调用,实参x、y将代换形参a、b。宏展开后该语句为“max=(xy)?x:y;”,用于计算x、y中的大数。由于宏定义非常容易出错,因此,对于带参的宏定义有以下问题需要特别说明。带参宏定义中,宏名和形参表之间不能有空格出现。例如:#defineMAX(a,b)(ab)?a:b写为:#defineMAX(a,b)(ab)?a:b这将被认为是无参宏定义,宏名MAX代表字符串(a,b)(ab)?a:b。宏展开时,宏调用语句“max=MAX(x,y);”将变为“max=(a,b)(ab)?a:b(x,y);”,这显然是错误的。在带参宏定义中,形式参数不分配内存单元,因此不必做类型定义。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行值传递。而在带参宏中,只是符号置换,不存在值传递的问题。在宏定义中的形参是标识符,而宏调用中的实参可以是表达式,例如:#defineSQ(y)(y)*(y)/*宏定义*/sq=SQ(a+1);/*宏调用*/127上例中第一行为宏定义,形参为y;而在宏调用中实参为a+1,是一个表达式,在宏展开时,用a+1代换y,再用(y)*(y)代换SQ,得到如下语句:sq=(a+1)*(a+1);这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再赋予形参,而宏代换中对实参表达式不作计算直接地照原样代换。在宏定义中,字符串内的形参通常要用括号括起来以避免出错。在上例中的宏定义中(y)*(y)表达式的y都用括号括起来,因此结果是正确的,如果去掉括号,把程序改为以下形式:#defineSQ(y)y*y/*宏定义无括号*/sq=SQ(a+1);/*宏调用*/这是由于置换只做简单的符号替换而不做其他处理而造成的,在宏替换之后将得到以下语句:sq=a+1*a+1;这显然与题意相违背,因此参数两边的括号是不能少的。其实,宏定义即使在参数两边加括号还是不够的,例如:#defineSQ(y)(y)*(y)/*宏定义有括号*/sq=160/SQ(a+1);/*宏调用依然出错*/读者可以分析一下宏调用语句,在宏代换之后变为:sq=160/(a+1)*(a+1);由于“/”和“*”运算符优先级和结合性相同,所以先做160/(a+1),再将结果与(a+1)相乘,所以程序运行的结果依然是错误的。那么,究竟怎样进行宏定义才能正确呢?下面是正确的宏定义:#defineSQ(y)((y)*(y))/*正确的宏定义*/sq=160/SQ(a+1);/*宏调用结果正确*/以上讨论说明,对于宏定义不仅应在参数两侧加括号,还应在整个符号串外加括号。带参宏和带参函数很相似,但有本质上的不同,除上面已谈到的各点外,把同一表达式用函数处理与用宏处理两者的结果有可能是不同的。例如有以下两段程序,第一个程序是采用调用函数的方式来实现的:/*程序1,函数调用*/intSQ(inty)/*函数定义*/{Return(y*y);}voidmain(){inti=1;while(i=5){printf(%d,SQ(i++));/*函数调用*/}128}下面的第二个程序是采用宏定义的方式来实现的:/*程序2,宏定义*/#defineSQ(y)((y)*(y))/*宏定义*/voidmain(){inti=1;while(i=5){printf(%d,SQ(i++));/*宏调用*/}}可以看到,不管是形参、实参还是具体的表达是都是一样的,但运行的结果却截然不同,函数调用的运行结果为:1491625而宏调用的运行结果却是:1925这是为什么呢?请读者先自己思考再看下面的分析。在第一个程序中,函数调用是把实参i值传给形参y后自增1,然后输出函数值,因而要循环5次,输出1~5的平方值。在第二个中宏调用时,实参和形参只作代换,因此SQ(i++)被代换为((i++)*(i++))。在第一次循环时,由于在其计算过程中i值一直为1,两相乘的结果为1,然后i值两次自增1,变为3。在第二次循环时,i值已有初值为3,同理相乘的结果为9,然后i再两次自增1变为5。进入第3次循环,由于i值已为5,所以这将是最后一次循环。相乘的结果为等于25。i值再两次自增1变为7,不再满足循环条件,停止循环。从以上分析可以看出函数调用和宏调用二者在形式上相似,在本质上是完全不同的,表3.2总结了宏与函数的不同之处。表3.2宏与函数的不同之处属性#define宏函数处理阶段预处理阶段,只是符号串的简单的置换编译阶段代码长度每次使用宏时,宏代码都被插入到程序中。因此,除了非常小的宏之外,程序的长度都将被大幅增长(除了inline函数之外)函数代码只出现在一个地方,每次使用这个函数,都只调用那个地方的同一份代码执行速度更快存在函数调用/返回的额外开销(inline函数除外)操作符优先级宏参数的求值是在所有周围表达式的上下文环境中,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果函数参数只在函数调用时求值一次,它的结果值传递给函数,因此,表达式的求值结果更容易预测参数求值参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能

1 / 17
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功