1, #表字符串化(stringfication),如
#define C(x) #x
C(I am a string) ---> "I am a string"
2, ##表字符串连接(concatenation)
左边的代码可以通过宏字符串连接来简化:
如#define cmd(x) x ##_command
cmd(quit) 就是 quit_command
实用的例子如下:
struct command
{
char *name;
void (*function) (void);
};
struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
...
};
改成如下:
#define COMMAND(NAME) { #NAME, NAME##_command }
struct command commands[] =
{
COMMAND(quit),
COMMAND(help),
};
下面的代码利用宏字符串化来输出信息:
#define WARN_IF(expr) do{ if(expr) {fprintf(stderr, "warning" #expr "\n");}}while(0)
使用do while的原因是为了让WARN_IF(expr)后面加分号使之看起来更像函数。
3,宏扩展
#include
#define str(s) #s
#define xstr(s) str(s)
#define foo 4
int main(int argc, char *argv[])
{
printf ("%s\n",str(foo));
printf ("%s\n",xstr(foo));
return 0;
}
# 结果为foo 4 宏展开时先展开里层的宏,但包含#和##的宏不展开,由于str(foo)宏str包含#故foo不展开,而xstr宏中不包含#故foo展开为4,即str(4)再开展str得4
同理可知下面结果为: _G(A,B) AB
#define G(a,b) a##b
#define _G(a,b) G(a,b)
#define S(X) #X
#define _S(X) S(X)
#define A 1
#define B 2
#include
int main()
{
printf("%s\n",S(_G(A,B)));
printf("%s\n",_S(G(A,B)));
return 0;
}
关于记号粘贴操作符(token paste operator): ##
1. 简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。
其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格
解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,
被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些
##来替代空格。
另外一些分隔标志是,包括操作符,比如 +, -, *, /, [,], …,所以尽管下面的
宏定义没有空格,但是依然表达有意义的定义: define add(a, b) a+b
而其强制连接的作用是,去掉和前面的字符串之间的空格,而把两者连接起来。
2. 举列 – 试比较下述几个宏定义的区别
#define A1(name, type) type name_##type##_type
#define A2(name, type) type name##_##type##_type
A1(a1, int); /* 等价于: int name_int_type; */
A2(a1, int); /* 等价于: int a1_int_type; */
解释:
1) 在第一个宏定义中,”name”和第一个”_”之间,以及第2个”_”和第二个
”type”之间没有被分隔,所以预处理器会把name_##type##_type解释成3段:
“name_”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过
的,所以它可以被宏替换。
2) 而在第二个宏定义中,“name”和第一个“_”之间也被分隔了,所以
预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type”
以及“_type”,这其间,就有两个可以被宏替换了。
3) A1和A2的定义也可以如下:
#define A1(name, type) type name_ ##type ##_type
<##前面随意加上一些空格>
#define A2(name, type) type name ##_ ##type ##_type
结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义
3. 其他相关 – 单独的一个 #
至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
#define __stringify_1(x) #x
那么
__stringify_1(linux) <==> ”linux”
所以,对于MODULE_DEVICE_TABLE
1) #define MODULE_DEVICE_TABLE(type,name) MODULE_GENERIC_TABLE(type##_device,name)
2) #define MODULE_GENERIC_TABLE(gtype,name) extern const struct gtype##_id __mod_##gtype##_table
__attribute__ ((unused, alias(__stringify(name))))
得到
MODULE_DEVICE_TABLE(usb, products)
/*notes: struct usb_device_id products; */
<==> MODULE_GENERIC_TABLE(usb_device,products)
<==> extern const struct usb_device_id __mod_usb_device_table
__attribute__ ((unused, alias(”products”)))
注意到alias attribute需要一个双引号,所以在这里使用了__stringify(name)来
给name加上双引号。另外,还注意到一个外部变量”__mod_usb_device_table”被alias
到了本驱动专用的由用户自定义的变量products
是如何使用的,更多的信息请参看《probe()过程分析》。
在标准C以及各中编译器中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义。
在ANSI C标准中定义了__FILE__,__LINE__,__DATA__,__TIME__,__STDC__等标准的预定义宏。GCC对其进行扩展,也定义了多个预定义宏。
概括起来GCC中可使用的预定义宏涵盖了如下几方面的信息:
1、宿主的信息:GNU的版本,编译器的版本,类型的相关信息,字节序信息等。
2、编译动作的信息:编译的日期、时间;编译时是否进行了时间或空间上的优化;定义的inline是否被编译器执行等。
3、文件的信息:文件名称、函数名称、行数信息、文件最后修改时间等等。
4、计数信息:__COUNTER__,__INCLUDE_LEVEL__等。
下面是一些常见的预定义宏的使用方法。
1、__FILE__,__LINE__,FUNCTION__
这是最常用到的预定义宏的组合,表示文件名、行数和函数名,用于程序运行期异常的跟踪。如:
//-------file main.c----------
#include
#include "myassert.h"
int func(const char *filename);
int main(int argc,char **argv)
{
MyAssert("two args are needed",argc==2);
func(argv[1]);
return 0;
}
//-------file func.c----------
#include
#include
#include
#include
#include "myassert.h"
int func(const char *filename)
{
int fd;
MyAssert("filename can not be null",filename);
MyAssert("file not exist",0==access(filename,F_OK));
fd = open(filename,O_RDONLY);
close(fd);
return 0;
}
//-------file myassert.h----------
#ifndef __MY_ASSERT_H__
#define __MY_ASSERT_H__
#include
#include
#define MyAssert(message,assertion) do{/
if(!(assertion)){/
printf("line %d in %s(%s)", __LINE__, __FILE__,__FUNCTION__);/
if(message){/
printf(" : %s",message);/
}/
printf("/n");/
abort();/
}/
}while(0);
#endif
#Makefile
TARGET = test
CC = gcc
CCFLAGS = -Wall
OBJS = main.o func.o
$(TARGET) : $(OBJS)
$(CC) -o $@ $(OBJS) $(CCFLAGS)
%.o : %.c
$(CC) -o $@ -c $< $(CCFLAGS)
clean:
rm -rf *.o
rm -rf $(TARGET)
可见通过使用__FILE__,__LINE__,FUNCTION__宏,可以帮助我们精确的定位出现异常的文件、函数和行数。
2、__BASE_FILE__
这个宏是和__FILE__相对应的,表示主输入文件的名字,对于源文件而言__FILE__和__BASE_FILE__是一样的;对于头文件二者才可能不同。比如在上个例子中,__LINE__这个宏是在myassert.h文件中定义的,被main.c和func.c包含之后__FILE__的值
分别变成了main.c和func.c。但是当我们希望知道MyAssert这个宏具体实在哪个文件(实际上是myassert.h)中定义的话,就需要用到__BASE_FILE__。
下面的例子可以帮助加深理解:
//-------file main.c----------
#include
#include "basefile.h"
int main(int argc, char *argv[])
{
printf("%s/n",sfile);
printf("%s/n",hfile);
return 0;
}
//-------file basefile.h----------
const char sfile[]= __FILE__;
const char hfile[]= __BASE_FILE__;
3、__DATE__,__TIME__
用于得到最后一次编译的日期和时间(字符串形式):
#include
//-------file main.c----------
int main()
{
printf("DATE : %s/n",__DATE__);
printf("TIME : %s/n",__TIME__);
}
4、__TIMESTAMP__
和__TIME__的格式相同。同于得到本文件最后一次被修改的时间。
5、__GNUC__、__GNUC_MINOR__、__GNUC_MINOR__、__GNUC_PATCHLEVEL__
用于得到GNU版本:
#include
int main()
{
if( __GNUC__ > 4 ||
(__GNUC__ == 4 && (__GNUC_MINOR__ > 2 ||
(__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ > 0)))){
printf("GNUC version is later than 3.3.2/n");
}else{
printf("GNUC version is older than 3.3.2/n");
}
}
6、__VERSION__
用于得到编译器的版本
//-------file main.c----------
#include
int main()
{
printf("Version : %s/n",__VERSION__);
return 0;
}
7、__COUNTER__
自身计数器,用于记录以前编译过程中出现的__COUNTER__的次数,从0开始计数。常用于构造一系列的变量名称,函数名称等。如:
//-------file main.c----------
#include
#define FUNC2(x,y) x##y
#define FUNC1(x,y) FUNC2(x,y)
#define FUNC(x) FUNC1(x,__COUNTER__)
int FUNC(var);
int FUNC(var);
int main() {
var0 = 0;
var1 = 1;
printf("%d/n",var0);
printf("%d/n",var1);
return 0;
}
8、__INCLUDE_LEVEL__
用于表示文件被包含的计数,从0开始递增,常作为递归包含的限制条件。如:
//-------file main.c----------
#include
int main()
{
#define REP_LIMIT 10
#define REP(BLAH) printf("%d ", BLAH);
#include "rep.h"
printf("/n");
return 0;
}
//--------file rep.h----------
#if __INCLUDE_LEVEL__ < REP_LIMIT
REP(__INCLUDE_LEVEL__)
#include "rep.h"
#endif
gcc main.c && ./a.out,得到结果:
1 2 3 4 5 6 7 8 9
在这个例子中文件rep.h自包含了9次,执行了9次REP(BLAH)。
实际上,__INCLUDE_LEVEL__最多的是和#include __FILE__组合使用,用于表示一个递归。如:
//-------file main.c----------
#ifndef AUTOINC
#define AUTOINC
#include
#define MAX_LEVEL 10
int main()
{
int i = 0;
#include __FILE__
printf("/n");
return 0;
}
#undef AUTOINC
#endif
#ifdef AUTOINC
#if __INCLUDE_LEVEL__ <= MAX_LEVEL
printf("%d ",__INCLUDE_LEVEL__);
#include __FILE__
#if __INCLUDE_LEVEL__ != MAX_LEVEL
printf("%d ",__INCLUDE_LEVEL__);
#endif
#endif
#endif
ANSI C标准中有几个标准预定义宏:
__FILE__ __DATE__ __TIME___ __LINE__ 等
__LINE__:在源代码中插入当前源代码行号;
__FILE__:在源文件中插入当前源文件名;
__DATE__:在源文件中插入当前的编译日期
__TIME__:在源文件中插入当前编译时间;
__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。
一、C预定义宏
C标准指定了一些预定义宏,编程中常常用到。
__DATE__ 进行预处理的日期
__FILE__ 代表当前源代码文件名的字符串
__LINE__ 代表当前源代码文件中行号的整数常量
__STDC__ 设置为1时,表示该实现遵循C标准
__STDC_HOSTED__ 为本机环境设置为,否则设为0
__STDC_VERSION__ 为C99时设置为199901L
__TIME__ 源文件的编译时间
__func__ C99提供的,为所在函数名的字符串
对于__FILE__,__LINE__,__func__这样的宏,在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的那一行,是哪个函数.
例如:
void why_me();
int main()
{
printf( "The file is %s/n", __FILE__ );
printf( "The date is %s/n", __DATE__ );
printf( "The time is %s/n", __TIME__ );
printf("The version is %s/n",__STDC__VERSION__);
printf( "This is line %d/n", __LINE__ );
printf( "This function is %s/n ", __func__ );
why_me();
return 0;
}
void why_me()
{
printf( "This function is %s/n", __func__ );
printf( "This is line %d/n", __LINE__ );
}
二、#line和#error
#line用于重置由__LINE__和__FILE__宏指定的行号和文件名。
用法如下:#line number filename
例如:#line 1000 //将当前行号设置为1000
#line 1000 "lukas.c" //行号设置为1000,文件名设置为lukas.c
#error指令使预处理器发出一条错误消息,该消息包含指令中的文本.这条指令的目的就是在程序崩溃之前能够给出一定的信息。
三、#pragma
在所有的预处理指令中,#Pragma 指令可能是最复杂的了。#pragma的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。
其格式一般为: #Pragma Para
其中Para 为参数,下面来看一些常用的参数。
(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_
X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。
(2)另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3)#pragma once (比较常用)
只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。
(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 )等价于:
#pragma warning(disable:4507 34) /* 不显示4507和34号警告信息。如果编译时总是出现4507号警告和34号警告,
而认为肯定不会有错误,可以使用这条指令。*/
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1---4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
//.......
#pragma warning( pop )
在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。
(7)pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。
(8)progma pack(n)
指定结构体对齐方式!#pragma pack(n)来设定变量以n字节对齐方式。n 字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为n的倍数。下面举例说明其用法。
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
为测试该功能,可以使用sizeof()测试结构体的长度。