C/C++预编译宏和命令

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 <stdio.h>
#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<stdio.h>
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<usb_device_id类型>。这个外部变量
是如何使用的,更多的信息请参看《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 <stdio.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#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 <stdio.h>
#include <stdlib.h>

#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 <stdio.h>
#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 <stdio.h>
//-------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 <stdio.h>
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 <stdio.h>
int main()
{
    printf("Version : %s/n",__VERSION__);
    return 0;
}

7、__COUNTER__
      自身计数器,用于记录以前编译过程中出现的__COUNTER__的次数,从0开始计数。常用于构造一系列的变量名称,函数名称等。如:

//-------file main.c----------
#include <stdio.h>

#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 <stdio.h>
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 <stdio.h>
#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()测试结构体的长度。

参考

  1. C++ 中常见预定义宏的使用

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值