C语言宏定义使用技巧.docx

上传人:b****0 文档编号:10084539 上传时间:2023-05-23 格式:DOCX 页数:11 大小:20.18KB
下载 相关 举报
C语言宏定义使用技巧.docx_第1页
第1页 / 共11页
C语言宏定义使用技巧.docx_第2页
第2页 / 共11页
C语言宏定义使用技巧.docx_第3页
第3页 / 共11页
C语言宏定义使用技巧.docx_第4页
第4页 / 共11页
C语言宏定义使用技巧.docx_第5页
第5页 / 共11页
C语言宏定义使用技巧.docx_第6页
第6页 / 共11页
C语言宏定义使用技巧.docx_第7页
第7页 / 共11页
C语言宏定义使用技巧.docx_第8页
第8页 / 共11页
C语言宏定义使用技巧.docx_第9页
第9页 / 共11页
C语言宏定义使用技巧.docx_第10页
第10页 / 共11页
C语言宏定义使用技巧.docx_第11页
第11页 / 共11页
亲,该文档总共11页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

C语言宏定义使用技巧.docx

《C语言宏定义使用技巧.docx》由会员分享,可在线阅读,更多相关《C语言宏定义使用技巧.docx(11页珍藏版)》请在冰点文库上搜索。

C语言宏定义使用技巧.docx

C语言宏定义使用技巧

写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性等等。

下面列举一些成熟软件中常用得宏定义……

   1,防止一个头文件被重复包含

   #ifndefCOMDEF_H

   #defineCOMDEF_H

   //头文件内容

   #endif

   2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

   typedefunsignedcharboolean;/*Booleanvaluetype.*/

   typedefunsignedlongintuint32;/*Unsigned32bitvalue*/

   typedefunsignedshortuint16;/*Unsigned16bitvalue*/

   typedefunsignedcharuint8;/*Unsigned8bitvalue*/

   typedefsignedlongintint32;/*Signed32bitvalue*/

   typedefsignedshortint16;/*Signed16bitvalue*/

   typedefsignedcharint8;/*Signed8bitvalue*/

   //下面的不建议使用

   typedefunsignedcharbyte;/*Unsigned8bitvaluetype.*/

   typedefunsignedshortword;/*Unsinged16bitvaluetype.*/

   typedefunsignedlongdword;/*Unsigned32bitvaluetype.*/

   typedefunsignedcharuint1;/*Unsigned8bitvaluetype.*/

   typedefunsignedshortuint2;/*Unsigned16bitvaluetype.*/

   typedefunsignedlonguint4;/*Unsigned32bitvaluetype.*/

   typedefsignedcharint1;/*Signed8bitvaluetype.*/

   typedefsignedshortint2;/*Signed16bitvaluetype.*/

   typedeflongintint4;/*Signed32bitvaluetype.*/

   typedefsignedlongsint31;/*Signed32bitvalue*/

   typedefsignedshortsint15;/*Signed16bitvalue*/

   typedefsignedcharsint7;/*Signed8bitvalue*/

   3,得到指定地址上的一个字节或字

   #defineMEM_B(x)(*((byte*)(x)))

   #defineMEM_W(x)(*((word*)(x)))

   4,求最大值和最小值

   #defineMAX(x,y)(((x)>(y))?

(x):

(y))

   #defineMIN(x,y)(((x)<(y))?

(x):

(y))

   5,得到一个field在结构体(struct)中的偏移量

   #defineFPOS(type,field)\

   /*lint-e545*/((dword)&((type*)0)->field)/*lint+e545*/

   6,得到一个结构体中field所占用的字节数

   #defineFSIZ(type,field)sizeof(((type*)0)->field)

   7,按照LSB格式把两个字节转化为一个Word

   #defineFLIPW(ray)((((word)(ray)[0])*256)+(ray)[1])

   8,按照LSB格式把一个Word转化为两个字节

   #defineFLOPW(ray,val)\

   (ray)[0]=((val)/256);\

   (ray)[1]=((val)&0xFF)

   9,得到一个变量的地址(word宽度)

   #defineB_PTR(var)((byte*)(void*)&(var))

   #defineW_PTR(var)((word*)(void*)&(var))

   10,得到一个字的高位和低位字节

   #defineWORD_LO(***)((byte)((word)(***)&255))

   #defineWORD_HI(***)((byte)((word)(***)>>8))

   11,返回一个比X大的最接近的8的倍数

   #defineRND8(x)((((x)+7)/8)*8)

   12,将一个字母转换为大写

   #defineUPCASE(c)(((c)>='a'&&(c)<='z')?

((c)-0x20):

(c))

   13,判断字符是不是10进值的数字

   #defineDECCHK(c)((c)>='0'&&(c)<='9')

   14,判断字符是不是16进值的数字

   #defineHEXCHK(c)(((c)>='0'&&(c)<='9')||\

   ((c)>='A'&&(c)<='F')||\

   ((c)>='a'&&(c)<='f'))

   15,防止溢出的一个方法

   #defineINC_SAT(val)(val=((val)+1>(val))?

(val)+1:

(val))

   16,返回数组元素的个数

   #defineARR_SIZE(a)(sizeof((a))/sizeof((a[0])))

   17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)

   #defineMOD_BY_POWER_OF_TWO(val,mod_by)\

   ((dword)(val)&(dword)((mod_by)-1))

   18,对于IO空间映射在存储空间的结构,输入输出处理

   #defineinp(port)(*((volatilebyte*)(port)))

   #defineinpw(port)(*((volatileword*)(port)))

   #defineinpdw(port)(*((volatiledword*)(port)))

   #defineoutp(port,val)(*((volatilebyte*)(port))=((byte)(val)))

   #defineoutpw(port,val)(*((volatileword*)(port))=((word)(val)))

   #defineoutpdw(port,val)(*((volatiledword*)(port))=((dword)(val)))

   19,使用一些宏跟踪调试

   ANSI标准说明了五个预定义的宏名。

它们是:

   _LINE_

   _FILE_

   _DATE_

   _TIME_

   _STDC_

   如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。

记住编译程序

   也许还提供其它预定义的宏名。

   _LINE_及_FILE_宏指令在有关#line的部分中已讨论,这里讨论其余的宏名。

   _DATE_宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

   源代码翻译到目标代码的时间作为串包含在_TIME_中。

串形式为时:

分:

秒。

   如果实现是标准的,则宏_STDC_含有十进制常量1.如果它含有任何其它数,则实现是非标准的。

   可以定义宏,例如:

   当定义了_DEBUG,输出数据信息和所在文件所在行

   #ifdef_DEBUG

   #defineDEBUGMSG(msg,date)printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)

   #else

   #defineDEBUGMSG(msg,date)

   #endif

   20,宏定义防止使用是错误

   用小括号包含。

   例如:

#defineADD(a,b)(a+b)

   用do{}while(0)语句包含多语句防止错误

   例如:

#difneDO(a,b)a+b;\

   a++;

   应用时:

if(…。

   DO(a,b);//产生错误

   else

C语言中如何使用宏

   C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。

下面对常遇到的宏的使用问题做了简单总结。

   宏使用中的常见的基础问题

   #符号和##符号的使用

   ……符号的使用

   宏的解释方法

   我们能碰到的宏的使用

   宏使用中的陷阱

   常见的基础性问题:

   关于#和##

   在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。

比如下面代码中的宏:

   #defineWARN_IF(EXP)    \

   do{if(EXP)    \fprintf(stderr,"Warning:

"#EXP"\n");}    \

   while(0)

   那么实际使用中会出现下面所示的替换过程:

   WARN_IF(divider==0);

   被替换为

   do{

   if(divider==0)

   fprintf(stderr,"Warning""divider==0""\n");

   }while(0);

   这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

   而##被称为连接符(concatenator),用来将两个Token连接为一个Token.注意这里连接的对象是Token就行,而不一定是宏的变量。

比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。

那么下面的代码就非常实用:

   structcommand

   {

   char*name;

   void(*function)(void);

   };

   #defineCOMMAND(NAME){NAME,NAME##_command}

   //然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:

   structcommandcommands[]={

   COMMAND(quit),

   COMMAND(help),

   ……

   }COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。

我们还可以n个##符号连接n+1个Token,这个特性也是#符号所不具备的。

比如:

   #defineLINK_MULTIPLE(a,b,c,d)a##_##b##_##c##_##d

   typedefstruct_record_typeLINK_MULTIPLE(name,company,position,salary);

   //这里这个语句将展开为:

   //    typedefstruct_record_typename_company_position_salary;

   关于……的使用

   ……在C宏中称为VariadicMacro,也就是变参宏。

比如:

   #definemyprintf(templt,……)fprintf(stderr,templt,__VA_ARGS__)

   //或者

   #definemyprintf(templt,args……)fprintf(stderr,templt,args)

   第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。

第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。

同C语言的stdcall一样,变参必须作为参数表的最有一项出现。

当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:

myprintf(templt,);

   的形式。

这时的替换过程为:

   myprintf("Error!

\n",);

   替换为:

   fprintf(stderr,"Error!

\n",);

   这是一个语法错误,不能正常编译。

这个问题一般有两个解决方法。

首先,GNUCPP提供的解决方法允许上面的宏调用写成:

   myprintf(templt);

   而它将会被通过替换变成:

   fprintf(stderr,"Error!

\n",);

   很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。

除了这种方式外,c99和GNUCPP都支持下面的宏定义方式:

   #definemyprintf(templt,……)fprintf(stderr,templt,##__VAR_ARGS__)

   这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。

那么此时的翻译过程如下:

   myprintf(templt);

   被转化为:

   fprintf(stderr,templt);

   这样如果templt合法,将不会产生编译错误。

   宏是如何解释的

   宏在日常编程中的常见使用

   宏使用中的陷阱

   这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

   错误的嵌套-Misnesting

   宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

   由操作符优先级引起的问题-OperatorPrecedenceProblem

   由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。

比如:

   #defineceil_div(x,y)(x+y-1)/y

   那么

   a=ceil_div(b&c,sizeof(int));

   将被转化为:

   a=(b&c    +sizeof(int)-1)/sizeof(int);

   //由于+/-的优先级高于&的优先级,那么上面式子等同于:

   a=(b&(c+sizeof(int)-1))/sizeof(int);

   这显然不是调用者的初衷。

为了避免这种情况发生,应当多写几个括号:

   defineceil_div(x,y)(((x)+(y)-1)/(y))

   消除多余的分号-SemicolonSwallowing

   通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

   MY_MACRO(x);

   但是如果是下面的情况:

   #defineMY_MACRO(x){\

   /*line1*/\

   /*line2*/\

   /*line3*/}

   //……

   if(condition())

   MY_MACRO(a);

   else

   {……}

   这样会由于多出的那个分号产生编译错误。

为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

   #defineMY_MACRO(x)do{

   /*line1*/\

   /*line2*/\

   /*line3*/}while(0)

   这样只要保证总是使用分号,就不会有任何问题。

   DuplicationofSideEffects

   这里的SideEffect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。

比如:

   #definemin(X,Y)((X)>(Y)?

(Y):

(X))

   //……

   c=min(a,foo(b));

   这时foo()函数就被调用了两次。

为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

   #definemin(X,Y)({\

   typeof(X)x_=(X);\

   typeof(Y)y_=(Y);\

   (x_

x_:

y_;})

   ({……})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2