全部博文(113)
分类: 嵌入式
2009-11-06 12:30:08
注意: 函数宏对参数类型是不敏感的, 你不必考虑将何种数据类型传递给宏. 那么, 如何构建对参数类型敏感的宏呢? 参考本章的第九部分, 关于"##"的介绍. |
关于定义宏的另外一些问题
(1) 宏可以被多次定义, 前提是这些定义必须是相同的. 这里的"相同"要求先后定义中空白符出现的位置相同, 但具体的空白符类型或数量可不同, 比如原先的空格可替换为多个其他类型的空白符: 可为tab, 注释...
e.g.
#define NULL 0
#define NULL
上面的重定义是相同的, 但下面的重定义不同:
#define fun(x) x+1
#define fun(x) x + 1 或: #define fun(y) y+1
如果多次定义时, 再次定义的宏内容是不同的, gcc会给出"NAME redefined"警告信息.
应该避免重新定义函数宏, 不管是在预处理命令中还是C语句中, 最好对某个对象只有单一的定义. 在gcc中, 若宏出现了重定义, gcc会给出警告.
(2) 在gcc中, 可在命令行中指定对象宏的定义:
e.g.
$ gcc -Wall -DMAX=100 -o tmp tmp.c
相当于在tmp.c中添加" #define MAX 100".
那么, 如果原先tmp.c中含有MAX宏的定义, 那么再在gcc调用命令中使用-DMAX, 会出现什么情况呢?
---若-DMAX=1, 则正确编译.
---若-DMAX的值被指定为不为1的值, 那么gcc会给出MAX宏被重定义的警告, MAX的值仍为1.
注意: 若在调用gcc的命令行中不显示地给出对象宏的值, 那么gcc赋予该宏默认值(1), 如: -DVAL == -DVAL=1
(3) #define所定义的宏的作用域
宏在定义之后才生效, 若宏定义被#undef取消, 则#undef之后该宏无效. 并且字符串中的宏不会被识别
e.g.
#define ON
sum = ON
#define TWO 2
sum = ON
#undef ON
sum = ON
char c[] = "TWO"
(4) 宏的替换可以是递归的, 所以可以嵌套定义宏.
e.g.
# define ON
# define NUMBER_1 1
int a = ON
2, #undef
#undef用来取消宏定义, 它与#define对立:
#undef name
如够被取消的宏实际上没有被#define所定义, 针对它的#undef并不会产生错误.
当一个宏定义被取消后, 可以再度定义它.
3, #if, #elif, #else, #endif
#if, #elif, #else, #endif用于条件编译:
#if 常量表达式1
#elif 常量表达式2
#elif 常量表达式3
...
#else
#endif
#if和#else分别相当于C语句中的if, else. 它们根据常量表达式的值来判别是否执行后面的语句. #elif相当于C中的else-if. 使用这些条件编译命令可以方便地实现对源代码内容的控制.
else之后不带常量表达式, 但若包含了常量表达式, gcc只是给出警告信息.
使用它们可以提升代码的可移植性---针对不同的平台使用执行不同的语句. 也经常用于大段代码注释.
e.g.
#if 0
{
}
#endif
常量表达式可以是包含宏, 算术运算, 逻辑运算等等的合法C常量表达式, 如果常量表达式为一个未定义的宏, 那么它的值被视为0.
#if MACRO_NON_DEFINED
在判断某个宏是否被定义时, 应当避免使用#if, 因为该宏的值可能就是被定义为0. 而应当使用下面介绍的#ifdef或#ifndef.
注意: #if, #elif, #else之后的宏只能是对象宏. 如果name为名的宏未定义, 或者该宏是函数宏. 那么在gcc中使用"-Wundef"选项会显示宏未定义的警告信息.
4, #ifdef, #ifndef, defined.
#ifdef, #ifndef, defined用来测试某个宏是否被定义
#ifdef name
它们经常用于避免头文件的重复引用:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
defined(name): 若宏被定义,则返回1, 否则返回0.
它与#if, #elif, #else结合使用来判断宏是否被定义, 乍一看好像它显得多余, 因为已经有了#ifdef和#ifndef. defined用于在一条判断语句中声明多个判别条件:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
和#if, #elif, #else不同, #indef, #ifndef, defined测试的宏可以是对象宏, 也可以是函数宏. 在gcc中使用"-Wundef"选项不会显示宏未定义的警告信息.
5, #include , #include_next
#include用于文件包含. 在#include 命令所在的行不能含有除注释和空白符之外的其他任何内容.
#include "headfile"
#include
#include
前面两种形式大家都很熟悉, "#include 预处理标记"中, 预处理标记会被预处理器进行替换, 替换的结果必须符合前两种形式中的某一种.
实际上, 真正被添加的头文件并不一定就是#include中所指定的文件. #include"headfile"包含的头文件当然是同一个文件, 但#include
关于#include "headfile"和#include
相对于#include, 我们对#include_next不太熟悉. #include_next仅用于特殊的场合. 它被用于头文件中(#include既可用于头文件中, 又可用于.c文件中)来包含其他的头文件. 而且包含头文件的路径比较特殊: 从当前头文件所在目录之后的目录来搜索头文件.
比如: 头文件的搜索路径一次为A,B,C,D,E. #include_next所在的当前头文件位于B目录, 那么#include_next使得预处理器从C,D,E目录来搜索#include_next所指定的头文件.
可参考进一步了解#include_next
6, 预定义宏
标准C中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义.
下面列出一些标准C中常见的预定义对象宏(其中也包含gcc自己定义的一些预定义宏:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
__STDC_VERSION__
__STDC_HOSTED__
gcc定义的预定义宏:
__OPTMIZE__
__OPTMIZE_SIZE__
__VERSION__
可参考"GCC the complete reference".
要想看到gcc所定义的所有预定义宏, 可以运行: $ cpp -dM /dev/null
7, #line
#line用来修改__LINE__和__FILE__.
e.g.
#line 100 "haha"
显示:
line: 34, file: 1.c
line: 100, file: haha
line: 101, file: haha
8, #pragma, _Pragma
#pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的如下:
#pragma GCC name token(s)
#pragma之后有两个部分: GCC和特定的pragma name. 下面分别介绍gcc中常用的.
(1) #pragma GCC dependency
dependency测试当前文件(既该语句所在的程序代码)与指定文件(既#pragma语句最后列出的文件)的时间戳. 如果指定文件比当前文件新, 则给出警告信息.
e.g.
在demo.c中给出这样一句:
#pragma GCC dependency "temp-file"
然后在demo.c所在的目录新建一个更新的文件: $ touch temp-file, 编译: $ gcc demo.c 会给出这样的警告信息:
如果当前文件比指定的文件新, 则不给出任何警告信息.
还可以在在#pragma中给添加自定义的警告信息.
e.g.
#pragma GCC dependency "temp-file" "demo.c needs to be updated!"
1.c:27:38: warning: extra tokens at end of #pragma directive
1.c:27:38: warning: current file is older than temp-file
注意: 后面新增的警告信息要用""引用起来, 否则gcc将给出警告信息.
(2) #pragma GCC poison token(s)
若源代码中出现了#pragma中给出的token(s), 则编译时显示警告信息. 它一般用于在调用你不想使用的函数时候给出出错信息.
e.g.
#pragma GCC poison scanf
scanf("%d", &a);
warning: extra tokens at end of #pragma directive
error: attempt to use poisoned "scanf"
注意, 如果调用了poison中给出的标记, 那么编译器会给出的是出错信息. 关于第一条警告, 我还不知道怎么避免, 用""将token(s)引用起来也不行.
(3) #pragma GCC system_header
从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码. 系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示. (除非用 #warning显式指明).
(这条#pragma语句还没发现用什么大的用处 )
由于#pragma不能用于宏扩展, 所以gcc还提供了_Pragma:
e.g.
#define PRAGMA_DEP #pragma GCC dependency "temp-file"
由于预处理之进行一次宏扩展, 采用上面的方法会在编译时引发错误, 要将#pragma语句定义成一个宏扩展, 应该使用下面的_Pragma语句:
#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")
注意, ()中包含的""引用之前引该加上\转义字符.
9, #, ##
#和##用于对字符串的预处理操作, 所以他们也经常用于printf, puts之类的字符串显示函数中.
#用于在宏扩展之后将tokens转换为以tokens为内容的字符串常量.
e.g.
#define TEST(a,b) printf( #a "<" #b "=%d\n", (a)<(b));
注意: #只针对紧随其后的token有效!
##用于将它前后的两个token组合在一起转换成以这两个token为内容的字符串常量. 注意##前后必须要有token.
e.g.
#define TYPE(type, n) type n
之后调用:
TYPE(int, a) = 1;
TYPE(long, b) = 1999;
将被替换为:
int a = 1;
long b = 1999;
(10) #warning, #error
#warning, #error分别用于在编译时显示警告和错误信息, 格式如下:
#warning tokens
#error tokens
e.g.
#warning "some warning"
注意, #error和#warning后的token要用""引用起来!
(在gcc中, 如果给出了warning, 编译继续进行, 但若给出了error, 则编译停止. 若在命令行中指定了 -Werror, 即使只有警告信息, 也不编译.