C语言宏的使用Word文件下载.docx
- 文档编号:4545454
- 上传时间:2023-05-03
- 格式:DOCX
- 页数:19
- 大小:27.23KB
C语言宏的使用Word文件下载.docx
《C语言宏的使用Word文件下载.docx》由会员分享,可在线阅读,更多相关《C语言宏的使用Word文件下载.docx(19页珍藏版)》请在冰点文库上搜索。
word;
/*Unsinged16bitvaluetype.*/
typedefunsignedlong
dword;
/*Unsigned32bitvaluetype.*/
uint1;
uint2;
/*Unsigned16bitvaluetype.*/
uint4;
int1;
/*Signed8bitvaluetype.*/
int2;
/*Signed16bitvaluetype.*/
typedeflongint
int4;
/*Signed32bitvaluetype.*/
typedefsignedlong
sint31;
sint15;
sint7;
3,得到指定地址上的一个字节或字
#defineMEM_B(x)(*((byte*)(x)))
#defineMEM_W(x)(*((word*)(x)))
4,求最大值和最小值
#defineMAX(x,y)(((x)>
(y))?
(x):
(y))
#defineMIN(x,y)(((x)<
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*)&
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'
9'
)
14,判断字符是不是16进值的数字
#defineHEXCHK(c)(((c)>
)||\
((c)>
A'
F'
((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)))
[2005-9-9添加]
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)
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"
}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!
);
替换为:
fprintf(stderr,"
这是一个语法错误,不能正常编译。
这个问题一般有两个解决方法。
首先,GNUCPP提供的解决方法允许上面的宏调用写成:
myprintf(templt);
而它将会被通过替换变成:
很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。
除了这种方式外,c99和GNUCPP都支持下面的宏定义方式:
#definemyprintf(templt,...)fprintf(stderr,templt,##__VAR_ARGS__)
这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。
那么此时的翻译过程如下:
被转化为:
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);
//由于+/-的优先级高于&
的优先级,n那么上面式子等同于:
(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{
/*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_<
y_)?
x_:
y_;
})
({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。
补充:
1、#define
命令#define定义了一个标识符及一个串。
在源程序中每次遇到该标识符时,均以定义的串代换它。
ANSI标准将标识符定义为宏名,将替换过程称为宏替换。
命令的一般形式为:
#defineidentifierstring
注意:
?
该语句没有分号。
在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。
宏名定义后,即可成为其它宏名定义中的一部分。
宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。
#defineXYZthisisatest,使用宏printf("
XYZ"
);
//该段不打印"
thisisatest"
而打印"
因为预编译器识别出的是"
如果串长于一行,可以在该行末尾用一反斜杠'
\'
续行。
2、#error
处理器命令#error强迫编译程序停止编译,主要用于程序调试。
3、#include
命令#include使编译程序将另一源文件嵌入带有#include的源文件,被读入的源文件必须用双引号或尖括号括起来。
#include"
stdio.h"
或者#include
这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。
将文件嵌入#include命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。
如果显式路径名为文件标识符的一部分,则仅在哪些子目录中搜索被嵌入文件。
否则,如果文件名用双引号括起来,则首先检索当前工作目录。
如果未发现文件,
则在命令行中说明的所有目录中搜索。
如果仍未发现文件,则搜索实现时定义的标准目录。
如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索。
如果文件没找到,则检索标准目录,不检索当前工作目录。
4、条件编译命令
有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。
商业软件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。
#if、#else,#elif及#endif
#if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。
命令#endif标识一个#if块的
结束。
#ifconstant-expression
statementsequence
跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。
表达式不许含有操作符sizeof(sizeof也是编译
时求值)。
#else命令的功能有点象C语言中的else;
#else建立另一选择(在#if失败的情况下)。
注意,#else属于#if块。
#elif命令意义与ELSEIF相同,它形成一个ifelse-if阶梯状语句,可进行多种编译选择。
#elif后跟一个常量表达式。
如果表达式为true,则编译其后的代码块,不对其它#elif表达式进行测试。
否则,顺序测试下一块。
#ifexpression
#elifexpression1
在嵌套的条件编译中#endif、#else或#elif与最近#if或#elif匹配。
#ifdef和#ifndef
条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"
如果有定义"
及"
如果无定义"
#ifdef的一般形式是:
#ifdefmacroname
#endif
#ifdef与#ifndef可以用于#if、#else,#elif语句中,但必须与一个#endif。
5、#undef
命令#undef取消其后那个前面已定义过有宏名定义。
一般形式为:
#undefmacroname
6、#line
命令#line改变__LINE__与__FILE__的内容,它们是在编译程序中预先定义的标识符。
命令的基本形式如下:
#linenumber["
filename"
]
其中的数字为任何正整数,可选的文件名为任意有效文件标识符。
行号为源程序中当前行号,文件名为源文件的名字。
命令#line主要用于调试及其它特殊
应用。
在#line后面的数字标识从下一行开始的数字标识。
7、预定义的宏名
ANSI标准说明了C中的五个预定义的宏名。
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
如果编译不是标准的,则可能仅支
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 使用