现在的位置: 首页 > 综合 > 正文

VC预处理指令与宏定义的妙用

2013年10月24日 ⁄ 综合 ⁄ 共 23807字 ⁄ 字号 评论关闭

VC中预处理指令与宏定义的妙用

  刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具。使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。

  一个经典的例子

  使用预处理与宏定义最经典的例子莫过于加在一个头文件中以避免头文件被两次编译。试想这种的情况,有一个文件headerfile.h 它被包含在headerfile1.h中,同时在headerfile2.h 中也被包含了,现在有一个CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h:

#include “headerfile1.h”
#include “headerfile2.h”

  假设headerfile.h 中定义了一个全局变量 iglobal 。

int iglobal;

  在编译的时候编译器两次编译headerfile,也就会发现iglobal被定义了两次,这时就会发生变量重定义的编译错误。

  传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只需要加上这么几行:

#ifndef smartnose_2002_6_21_headerfile_h
#define smartnose_2002_6_21_headerfile_h

int iglobal;

#endif

  仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h以后,smartnose_2002_6_21_headerfile_h 这个宏就被定义了,以后对headerfile.h的编译都会跳过int iglobal 这一行。当然smartnose_2002_6_21_headerfile_h 这个宏是可以任意定义的,但是这个宏本身不能和其它文件中定义的宏重复,所以MFC在自动生成的文件中总是使用一个随机产生的长度非常长的宏,但我觉得这没有必要,我建议在这个宏中加入一些有意义的信息,比方作者,文件名,文件创建时间等等,因为我们有时候会忘记在注释中加入这些信息。

  在VC.Net 中我们不会再看见这些宏定义了,因为在这里会普遍使用一个预处理指令:

#pragma once

  只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

  源代码版本控制

  当我们为许多平台开发多个版本的时候预编译指令和宏定义也能够帮我们的忙。假设我们现在为WINDOWS 和LINUX开发了一套软件,由于这两种系统的不同,我们不得不在程序控制源代码的版本。比方内存的分配,我们可以在LINUX上使用标准C的malloc 函数,但是我们希望在 WINDOWS上使用HeapAlloc API。下面的代码演示了这种情况:

main()
{
………………..
#ifdef _WINDOWS_PLATFORM
HeapAlloc(5);
#else
malloc(5);
#endif
………………..
}

  当我们在WINDOWS 平台上编译此程序的时候,只需要定义_WINDOWS_PLATFORM这个宏,那么HeapAlloc这条语句就能够起作用了。这样就能够让我们在同一个文件中为不同的平台实现不同版本的代码,同时保持程序的良好结构。在许多情况下,我们还可以为一个方法使用不同的算法,然后用宏定义来针对不同的情况选择其中的一个进行编译。这在MFC应用程序中是使用得最多的。最明显的就是文件中经常存在的

#ifdef _DEBUG

…………………….some code………..

#endif

  这样的代码,这些代码在应用程序的调试版(DEBUG)中会发挥其作用。

  #Pragma 指令

  在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。其格式一般为

  #Pragma Para

  其中Para 为参数,下面来看一些常用的参数。

  message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:

#Pragma message(“消息文本”)

  当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

  当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

#ifdef _X86

#Pragma message(“_X86 macro activated!”)

#endif

  当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。

  另一个使用得比较多的pragma参数是code_seg。格式如:

#pragma code_seg( ["section-name"[,"section-class"] ] )

  它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

  最后一个比较常用的就是上面所说的#pragma once 指令了。

  VC预定义的宏

  在VC中有一类宏并不是由用户用#define语句定义的,而是编译器本身就能够识别它们。这些宏的作用也是相当大的。让我们来看第一个,也是MFC中使用得最频繁的一个:__FILE__ 。

  当编译器遇到这个宏时就把它展开成当前被编译文件的文件名。好了,我们马上就可以想到可以用它来做什么,当应用程序发生错误时,我们可以报告这个错误发生的程序代码在哪个文件里,比方在文件test.cpp中有这样的代码:

try
{
char * p=new(char[10]);
}
catch(CException *e )
{
TRACE(“ there is an error in file: %s\n”,__FILE__);
}

  在程序运行的时候,如果内存分配出现了错误,那么在调试窗口中会出现there is an error in file: test.cpp 这句话,当然,我们还可以把这个错误信息显示在别的地方。

  如果我们还能够记录错误发生在哪一行就好了,幸运的是,与__FILE__宏定义一样,还有一个宏记录了当前代码所在的行数,这个宏是__LINE__。使用上面的两个宏,我们可以写出一个类似于VC提供的ASSERT语句。下面是方法

#define MyAssert(x) \
if(!(x)) \
MessageBox(__FILE__,__LINE__,NULL,MB_OK);

  我们在应用程序中可以象使用ASSERT语句一样使用它,在错误发生时,它会弹出一个对话框,其标题和内容告诉了我们错误发生的文件和代码行号,方便我们的调试,这对于不能使用ASSERT语句的项目来说是非常有用的。

  除了这两个宏以外,还有记录编译时间的__TIME__,记录日期的__DATE__,以及记录文件修改时间的__TIMESTAMP__宏。

  使用这些预定义的宏,我们几乎可以生成和VC能够生成的一样完整的源代码信息报表。

  结论

  翻开MFC和Linux的源代码,宏定义几乎占据了半边天,消息映射,队列操作,平台移植,版本管理,甚至内核模块的拆卸安装都用宏定义完成。毫不夸张的说,有些文件甚至就只能看见宏定义。所以学习宏定义,熟练的使用宏定义对于学习C语言乃至VC都是非常关键的。

一、标准预定义宏
The standard predefined macros are specified by the relevant language standards, so they are available with all compilers that implement those standards. Older compilers may not provide all of them. Their names all start with double underscores.

__FILE__
This macro expands to the name of the current input file, in the form of a C string constant. This is the path by which the preprocessor opened the file, not the short name specified in #include or as the input file name argument. For example, "/usr/local/include/myheader.h"
is a possible expansion of this macro.

__LINE__
This macro expands to the current input line number, in the form of a decimal integer constant. While we call it a predefined macro, it's a pretty strange macro, since its "definition" changes with each new line of source code.
__FILE__ and __LINE__ are useful in generating an error message to report an inconsistency detected by the program; the message can state the source line at which the inconsistency was detected. For example,

     fprintf (stderr, "Internal error: "
                      "negative string length "
                      "%d at %s, line %d.",
              length, __FILE__, __LINE__);
   
An #include directive changes the expansions of __FILE__ and __LINE__ to correspond to the included file. At the end of that file, when processing resumes on the input file that contained the #include directive, the expansions of __FILE__ and __LINE__ revert
to the values they had before the #include (but __LINE__ is then incremented by one as processing moves to the line after the #include).

A #line directive changes __LINE__, and may change __FILE__ as well. See Line Control.

C99 introduces __func__, and GCC has provided __FUNCTION__ for a long time. Both of these are strings containing the name of the current function (there are slight semantic differences; see the GCC manual). Neither of them is a macro;
the preprocessor does not know the name of the current function. They tend to be useful in conjunction with __FILE__ and __LINE__, though.

__DATE__
This macro expands to a string constant that describes the date on which the preprocessor is being run. The string constant contains eleven characters and looks like "Feb 12 1996". If the day of the month is less than 10, it is padded with a space on the left.
If GCC cannot determine the current date, it will emit a warning message (once per compilation) and __DATE__ will expand to "??? ?? ????".

__TIME__
This macro expands to a string constant that describes the time at which the preprocessor is being run. The string constant contains eight characters and looks like "23:59:01".
If GCC cannot determine the current time, it will emit a warning message (once per compilation) and __TIME__ will expand to "??:??:??".

__STDC__
In normal operation, this macro expands to the constant 1, to signify that this compiler conforms to ISO Standard C. If GNU CPP is used with a compiler other than GCC, this is not necessarily true; however, the preprocessor always conforms to the standard unless
the -traditional-cpp option is used.
This macro is not defined if the -traditional-cpp option is used.

On some hosts, the system compiler uses a different convention, where __STDC__ is normally 0, but is 1 if the user specifies strict conformance to the C Standard. CPP follows the host convention when processing system header files, but
when processing user files __STDC__ is always 1. This has been reported to cause problems; for instance, some versions of Solaris provide X Windows headers that expect __STDC__ to be either undefined or 1. See Invocation.

__STDC_VERSION__
This macro expands to the C Standard's version number, a long integer constant of the form yyyymmL where yyyy and mm are the year and month of the Standard version. This signifies which version of the C Standard the compiler conforms to. Like __STDC__, this
is not necessarily accurate for the entire implementation, unless GNU CPP is being used with GCC.
The value 199409L signifies the 1989 C standard as amended in 1994, which is the current default; the value 199901L signifies the 1999 revision of the C standard. Support for the 1999 revision is not yet complete.

This macro is not defined if the -traditional-cpp option is used, nor when compiling C++ or Objective-C.

__STDC_HOSTED__
This macro is defined, with value 1, if the compiler's target is a hosted environment. A hosted environment has the complete facilities of the standard C library available.

__cplusplus
This macro is defined when the C++ compiler is in use. You can use __cplusplus to test whether a header is compiled by a C compiler or a C++ compiler. This macro is similar to __STDC_VERSION__, in that it expands to a version number. A fully conforming implementation
of the 1998 C++ standard will define this macro to 199711L. The GNU C++ compiler is not yet fully conforming, so it uses 1 instead. We hope to complete our implementation in the near future.

__OBJC__
This macro is defined, with value 1, when the Objective-C compiler is in use. You can use __OBJC__ to test whether a header is compiled by a C compiler or a Objective-C compiler.

__ASSEMBLER__
This macro is defined with value 1 when preprocessing assembly language.

昨天写代码时需要在代码获取当前编译时间,从而可动态地作为版本信息,因此用到了C标准中的一些预定义的宏。在此将C标准中定义的几个宏一并总结一下:

__DATE__ 进行预处理的日期(“Mmm dd yyyy”形式的字符串文字,如May 27 2006)
__FILE__ 代表当前源代码文件名的字符串文字 ,包含了详细路径,如G:/program/study/c+/test1.c
__LINE__ 代表当前源代码中的行号的整数常量
__TIME__ 源文件编译时间,格式微“hh:mm:ss”,如:09:11:10;
__func__ 当前所在函数名,在编译器的较高版本中支持
__FUNCTION__ 当前所在函数名

  对于__FILE__,__LINE__,__func__,__FUNCTION__ 这样的宏,在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的那一行,是哪个函数。
而对于__DATE__,__TIME__则可以获取编译时间,如如下代码通过宏获取编译时间,并通过sscanf()从中获取具体的年月日时分秒数据,可在代码中做相应使用。我的代码中是根据此数据作为版本标识,并依此判断哪个版本新些及是否需要升级。
char * creationDate   = __DATE__ ", " __TIME__;
sscanf(creationDate, "%s %d %d, %d:%d:%d", month, &day, &year, &hour, &min, &sec);

预处理命令#pragma和预定义宏--转载

一、C预定义宏
C标准指定了一些预定义宏,编程中常常用到。
__DATE__     进行预处理的日期
__FILE__     代表当前源代码文件名的字符串
__LINE__     代表当前源代码文件中行号的整数常量
__STDC__     设置为1时,表示该实现遵循C标准
__STDC_HOSTED__  为本机环境设置为,否则设为0
__STDC_VERSION__ 为C99时设置为199901L
__TIME__     源文件的编译时间
__func__     C99提供的,为所在函数名的字符
对于__FILE____LINE____func__这样的宏,在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的那一行,是哪个函数.

例如:
#include
#include
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()测试结构体的长度!

在你写dll的时候,因为对于C和C++,编译器会有不同的名字解析规则,所以可以这样用

#ifndef   __STDC__

extern "C "   void   function();

#else

void   function();

#endif

__LINE__           在源代码中插入当前源代码行号
  __FILE__           在源代码中插入当前源代码文件名
  __DATE__           在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
  __TIME__           在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕 
  __STDC__           当要求程序严格遵循ANSIC标准时该标识符被赋值为1。
----------------------------------------------------------------------------

标识符__LINE__和__FILE__通常用来调试程序;标识符__DATE__和__TIME__通常用来在编译后的程序中加入一个时间标志,以区分程序的不同版本;当要求程序严格遵循ANSIC标准时,标识符__STDC__就会被赋值为1;当用C++编译程序编译时,标识符__cplusplus就会被定义。

#include

int main ()

{

    printf("该输出行在源程序中的位置:%d\n", __LINE__ );

    printf("该程序的文件名为:%s\n", __FILE__ );

    printf("当前日期为:%s\n", __DATE__ );

    printf("当前时间为:%s\n", __TIME__ );

    return 0;

}

#include

void main(void)

{

    printf("%d",__LINE__); // Line 5

}

结果为:5

// 标准预定义宏宏.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include

void main(void)

{

    printf("%d",__LINE__); // Line 5

}

编译器宏使用总结
 
 

C/C++中宏总结C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性。ANSI标准定义的C语言预处理程序包括下列命令:

#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。

命令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为:

#define identifier string

注意:

? 该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。

? 宏名定义后,即可成为其它宏名定义中的一部分。

? 宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。例如:

#define XYZ this is a tes

使用宏printf("XYZ");//该段不打印"this is a test"而打印"XYZ"。因为预编译器识别出的是"XYZ"

? 如果串长于一行,可以在该行末尾用一反斜杠' \'续行。

处理器命令#error强迫编译程序停止编译,主要用于程序调试。

#include 命令#i nclude使编译程序将另一源文件嵌入带有#i nclude的源文件,被读入的源文件必须用双引号或尖括号括起来。例如:

#i nclude"stdio.h"或者#i nclude

这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。

将文件嵌入#i nclude命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。

如果显式路径名为文件标识符的一部分,则仅在哪些子目录中搜索被嵌入文件。否则,如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,则在命令行中说明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义的标准目录。

如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索

如果文件没找到,则检索标准目录,不检索当前工作目录。

条件编译命令

有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。

#if、#else,#elif及#endif

#if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。命令#endif标识一个#if块的结束。

#if constant-expression

statement sequence

#endif

跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。表达式不许含有操作符sizeof(sizeof也是编译时求值)。

#else命令的功能有点象C语言中的else;#else建立另一选择(在#if失败的情况下)。注意,# else属于# if块。

#elif命令意义与ELSE IF 相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。#elif 后跟一个常量表达式。如果表达式为true,则编译其后的代码块,不对其它#elif表达式进行测试。否则,顺序测试下一块。

#if expression

statement sequence

#elif expression1

statement sequence

#endif

在嵌套的条件编译中#endif、#else或#elif与最近#if或#elif匹配。

# ifdef 和# ifndef

条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"如果有定义"及"如果无定义"。# ifdef的一般形式是:

# ifdef macroname

statement sequence

#endif

#ifdef与#ifndef可以用于#if、#else,#elif语句中,但必须与一个#endif。

命令#undef 取消其后那个前面已定义过有宏名定义。一般形式为:

#undef macroname

命令# line改变__LINE__与__FILE__的内容,它们是在编译程序中预先定义的标识符。命令的基本形式如下:

# line number["filename"]

其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前行号,文件名为源文件的名字。命令# line主要用于调试及其它特殊应用。注意:在#line后面的数字标识从下一行开始的数字标识。

预定义的宏名

ANSI标准说明了C中的五个预定义的宏名。它们是:

__LINE__

__FILE__

__DATE__

__TIME__

__STDC__

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。

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

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

源代码翻译到目标代码的时间作为串包含在__TIME__中。串形式为时:分:秒。

如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。编译C++程序时,编译器自动定义了一个预处理名字__cplusplus,而编译标准C时,自动定义名字__STDC__。

注意:宏名的书写由标识符与两边各二条下划线构成。

(部分内容出自:http://www.bc-cn.net/Article/kfyy/cyy/jc/200511/919.html) 8、

C、C++宏体中出现的#,#@,##

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

##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体

上一篇文章中,我演示了几个常用的宏定义和预处理指令,但可以说这些都是相当常规的技巧。下面要介绍的宏定义与预处理指令的用法也是ATL,MFC以及LINUX中使用得比较多的非常重要的技巧。

  ## 连接符与# 符

  ## 连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工作的。这是MSDN上的一个例子。

  假设程序中已经定义了这样一个带参数的宏:

#define paster( n ) printf( "token" #n " = %d", token##n )

  同时又定义了一个整形变量:

int token9 = 9;

  现在在主程序中以下面的方式调用这个宏:

paster( 9 );

  那么在编译时,上面的这句话被扩展为:

printf( "token" "9" " = %d", token9 );

  注意到在这个例子中,paster(9);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从而成为了token9。而#n也被”9”所替代。

  可想而知,上面程序运行的结果就是在屏幕上打印出token9=9

  在ATL的编程中,我们查看它的源代码就会经常看见这样的一段:

#define IMPLEMENTS_INTERFACE(Itf) \
{&IID_##Itf, ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls, Itf) },

  我们经常不假思索的这样使用它:

……
IMPLEMENTS_INTERFACE(ICat)
……

  实际上IID_ICat 已经在别的地方由ATL向导定义了。当没有向导的时候,你只要遵循把IID_加在你的接口名前面来定义GUID的规则就也可以使用这个宏。在实际的开发过程中可能很少用到这种技巧,但是ATL使用得如此广泛,而其中又出现了不少这样的源代码,所以明白它是怎么一回事也是相当重要的。我的一个朋友就是因为不知道IMPLEMENTS_INTERFACE宏是怎么定义的,而又不小心改动了IID_ICat的定义而忙活了一整天。

  Linux的怪圈

  在刚开始阅读Linux的时候有一个小小的宏让我百思不得其解:

#define wait_event(wq,condition) \
do{ \
if(condition) \
break; \
__wait_event(wq,condition); \
}while(0)

  这是一个奇怪的循环,它根本就只会运行一次,为什么不去掉外面的do{..}while结构呢?我曾一度在心里把它叫做“怪圈”。原来这也是非常巧妙的技巧。在工程中可能经常会引起麻烦,而上面的定义能够保证这些麻烦不会出现。下面是解释:

  假设有这样一个宏定义

#define macro(condition) \

if(condition) dosomething();

  现在在程序中这样使用这个宏:

if(temp)
macro(i);
else
doanotherthing();

  一切看起来很正常,但是仔细想想。这个宏会展开成:

if(temp)
if(condition) dosomething();
else
doanotherthing();

  这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译通过了,但是运行的结果一定是错误的。

  为了避免这个错误,我们使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。

  几个小小的警告

  正如微软声称的一样,宏定义与预编译器指令是强大的,但是它又使得程序难以调试。所以在定义宏的时候不要节省你的字符串,一定要力争完整的描述这个宏的功能。同时在定义宏的时候如有必要(比方使用了if语句)就要使用do{…}while(0)将它封闭起来。在宏定义的时候一定要注意各个宏之间的相互依赖关系,尽量避免这种依赖关系的存在。下面就有这样一个例子。

  设有一个静态数组组成的整型队列,在定义中使用了这样的方法: int array[]={5, 6, 7, 8};

  我们还需要在程序中遍历这个数组。通常的做法是使用一个宏定义

#define ELE_NUM 4
…………………………..
……………………………..

for(int I=0;I<ELE_NUM;I++)
{
cout<<array[I];
}

  由于某种偶然的原因,我们删除了定义中的一个元素,使它变成:

array[]={5,6,7}

  而却忘了修改ELE_NUM的值。那么在上面的代码中马上就会发生访问异常,程序崩溃。然后是彻夜不眠的调试,最后发现问题出在这个宏定义上。解决这个问题的方法是不使用

array[]={….}这样的定义,而显式的申明数组的大小:

array[ELE_NUM]={….}

  这样在改动数组定义的时候,我们就不会不记得去改宏定义了。总之,就是在使用宏定义的时候能够用宏定义的地方统统都用上。

  我发现的另一个有趣的现象是这样的:

  假设现在有一个课程管理系统,学生的人数用宏定义为:

#define STU_NUM 50

  而老师的人数恰好也是50人,于是很多人把所有涉及到老师人数的地方通通用上STU_NUM这个宏。另一个学期过去,学生中的一个被开除了,系统需要改变。怎么办呢?简单的使用#define STU_NUM 49 么?如果是这样,一个老师也就被开除了,我们不得不手工在程序中去找那些STU_NUM宏然后判断它是否是表示学生的数目,如果是,就把它改成49。天哪,这个宏定义制造的麻烦比使用它带来的方便还多。正确的方法应该是为老师的数目另外定义一个宏:

#define TEA_NUM 50

  当学生的数目改变以后只要把STU_NUM 定义为49就完成了系统的更改。所以,当程序中的两个量之间没有必然联系的时候一定不要用其中的一个宏去替代另一个,那只会让你的程序根本无法改动。

  最后,建议C/C++语言的初学者尽可能多的在你的程序中使用宏定义和预编译指令。多看看MFC,ATL或者LINUX的源代码,你会发现C语言强大的原因所在。

VC 编译连接(#if #ifdef #ifndef #else #endif defened) 

2009-11-23 14:49:10|  分类:技术
标签:
|字号
订阅

一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

条件编译的一般形式:

(1)                         (2)                               (3)

#ifdef 标识符          #ifndef 标识符              #if 表达式

程序段1                 程序段1                       程序段1

#else                     #else                            #else

   程序段2                程序段2                       程序段2

#endif                    #endif                         #endif

形式(1),它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序1:开关作用,条件编程
#ifdef A
程序段
#endif
2:避免重复定义,重复包舍
#ifndef A
#defined A
程序段
#endif段2。例如,我们有一个数据类型,在Windows平台中,应该使用long类型表示,而在其他平台应该使用float表示,这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译:

    #ifdef WINDOWS

    #define MYTYPE long

    #else

    #define MYTYPE float

    #endif

     如果在windows平台上编译程序,则在程序开始时定义windows。#define WINDOWs则在程序中MYTYPE long就会被定义,#define MYTYPE long。如果 #define WINDOWS 0 则在程序中MYTYPE floa就会被定义,#define MYTYPE float。

    形式(2),只是第一行与第一种形式不同:将“ifdef”改为“ifndef”。它的作用是:若标识符未被定义则编译程序段1,否则编译程序段2。这种形式与第一种形式的作用相反。

   

    形式(3),当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。例如,咱们在VC的头文件中程序中经常看到这样的例子:

#if !defined(AFX_LOAD_H__98934971_8314_4B1A_AFC6_7033D48189B1__INCLUDED_)

#define AFX_LOAD_H__98934971_8314_4B1A_AFC6_7033D48189B1__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

    如果!defined后面的没有定义,则执行下面语句进行定义。

常用方法:

1:开关作用,条件编程
            #ifdef A
            程序段
            #endif
        2:避免重复定义,重复包舍
           #ifndef A
           #defined A
           程序段
           #endif

#ifdef,指示符,宏定义

使用#ifdef指示符,我们可以区隔一些与特定头文件、程序库和其他文件版本有关的代码。代码举例:新建define.cpp文件

  #include "iostream.h"

  int main()

  {

  #ifdef DEBUG

  cout<< "Beginning execution of main()";

  #endif

  return 0;

  }

  运行结果为:

  Press any key to continue

  改写代码如下:

  #include "iostream.h"

  #define DEBUG

  int main()

  {

  #ifdef DEBUG

  cout<< "Beginning execution of main()";

  #endif

  return 0;

  }

  运行结果为:

  Beginning execution of main()

  Press any key to continue

  更一般的情况是,#define语句是包含在一个特定的头文件中。比如,新建头文件head.h,在文件中加入代码:

  #ifdef DEBUG

  #define DEBUG

  #endif

  而在define.cpp源文件中,代码修改如下:

  #include "iostream.h"

  #include "head.h"

  int main()

  {

  #ifdef DEBUG

  cout<< "Beginning execution of main()";

  #endif

  return 0;

  }

  运行结果如下:

  Beginning execution of main()

  Press any key to continue

  结论:

  通过使用#ifdef指示符,我们可以区隔一些与特定头文件、程序库和其他文件版本有关的代码。

  C语言之详解#ifdef等宏

  这几个宏是为了进行条件编译。一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

  条件编译命令最常见的形式为:

  #ifdef 标识符

  程序段1

  #else

  程序段2

  #endif

  它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。

  其中#else部分也可以没有,即:

  #ifdef

  程序段1

  #endif

  这里的“程序段”可以是语句组,也可以是命令行。这种条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系统上系统上运行,而不同的计算机又有一定的差异。例如,我们有一个数据类型,在Windows平台中,应该使用long类型表示,而在其他平台应该使用float表示,这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译:

  #ifdef WINDOWS

  #define MYTYPE long

  #else

  #define MYTYPE float

  #endif

  如果在Windows上编译程序,则可以在程序的开始加上

  #define WINDOWS

  这样则编译下面的命令行:

  #define MYTYPE long

  如果在这组条件编译命令之前曾出现以下命令行:

  #define WINDOWS 0

  则预编译后程序中的MYTYPE都用float代替。这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单的情况,可以根据此思路设计出其它的条件编译。

  例如,在调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段:

  #ifdef DEBUG

  print ("device_open(%p)\n", file);

  #endif

  如果在它的前面有以下命令行:

  #define DEBUG

  则在程序运行时输出file指针的值,以便调试分析。调试完成后只需将这个define命令行删除即可。有人可能觉得不用条件编译也可达此目的,即在调试时加一批printf语句,调试后一一将printf语句删除去。的确,这是可以的。但是,当调试时加的printf语句比较多时,修改的工作量是很大的。用条件编译,则不必一一删改printf语句,只需删除前面的一条“#define DEBUG”命令即可,这时所有的用DEBUG作标识符的条件编译段都使其中的printf语句不起作用,即起统一控制的作用,如同一个“开关”一样。

  有时也采用下面的形式:

  #ifndef 标识符

  程序段1

  #else

  程序段2

  #endif

  只是第一行与第一种形式不同:将“ifdef”改为“ifndef”。它的作用是:若标识符未被定义则编译程序段1,否则编译程序段2。这种形式与第一种形式的作用相反。

  以上两种形式用法差不多,根据需要任选一种,视方便而定。

  还有一种形式,就是#if后面的是一个表达式,而不是一个简单的标识符:

  #if 表达式

  程序段1

  #else

  程序段2

  #endif

  它的作用是:当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。

  例如:输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母输出。

  #define LETTER 1

  main()

  {

  char str[20]="C Language",c;

  int i=0;

  while((c=str[i])!='\0'){

  i++;

  #if LETTER

  if(c>='a'&&c<='z') c=c-32;

  #else

  if(c>='A'&&c<='Z') c=c+32;

  #endif

  printf("%c",c);

  }

  }

  运行结果为:C LANGUAGE

  现在先定义LETTER为1,这样在预处理条件编译命令时,由于LETTER为真(非零),则对第一个if语句进行编译,运行时使小写字母变大写。如果将程序第一行改为:

  #define LETTER 0

  则在预处理时,对第二个if语句进行编译处理,使大写字母变成小写字母(大写字母与相应的小写字母的ASCII代码差32)。此时运行情况为:

  c language

  有人会问:不用条件编译命令而直接用if语句也能达到要求,用条件编译命令有什么好处呢?的确,此问题完全可以不用条件编译处理,但那样做目标程序长(因为所有语句都编译),而采用条件编译,可以减少被编译的语句,从而减少目标的长度。当条件编译段比较多时,目标程序长度可以大大减少。

详细讲解   Visual   C++   中的预处理器的编译指示指令   #pragma   pack([n])

  该指令指定结构和联合成员的紧凑对齐。而一个完整的转换单元的结构和联合的紧凑对齐由   /Zp   选项设置。紧凑对齐用   pack   编译指示在数据说明层设置。该编译指示在其出现后的第一个结构或联合说明处生效。该编译指示对定义无效。当你使用   #pragma   pack(n)   时,这里   n   为   1、2、4、8   或   16。第一个结构成员之后的每个结构成员都被存储在更小的成员类型或   n   字节界限内。如果你使用无参量的   #pragma   pack,结构成员被紧凑为以  
/Zp   指定的值。该缺省   /Zp   紧凑值为   /Zp8。

  编译器也支持以下增强型语法:
#pragma   pack([[{push|pop},][标识符,]][n])
  若不同的组件使用   pack   编译指示指定不同的紧凑对齐,这个语法允许你把程序组件组合为一个单独的转换单元。
  带   push   参量的   pack   编译指示的每次出现将当前的紧凑对齐存储到一个内部编译器堆栈中。编译指示的参量表从左到右读取。如果你使用   push,则当前紧凑值被存储起来;如果你给出一个   n   的值,该值将成为新的紧凑值。若你指定一个标识符,即你选定一个名称,则该标识符将和这个新的的紧凑值联系起来。
  带一个   pop   参量的   pack   编译指示的每次出现都会检索内部编译器堆栈顶的值,并且使该值为新的紧凑对齐值。如果你使用   pop   参量且内部编译器堆栈是空的,则紧凑值为命令行给定的值,并且将产生一个警告信息。若你使用   pop   且指定一个   n   的值,该值将成为新的紧凑值。
  若你使用   pop   且指定一个标识符,所有存储在堆栈中的值将从栈中删除,直到找到一个匹配的标识符,这个与标识符相关的紧凑值也从栈中移出,并且这个仅在标识符入栈之前存在的紧凑值成为新的紧凑值。如果未找到匹配的标识符,将使用命令行设置的紧凑值,并且将产生一个一级警告。缺省紧凑对齐为8。

  pack   编译指示的能让你编写头文件,确保在遇到该头文件的前后的紧凑值是一样的。
/*
File   name:   include1.h
*/
#pragma   pack(push,enter_include1)
/*   你的包括文件代码...   */
#pragma   pack(pop,   enter_include1)
/*   include1.h结束   */
  在上面的例子中,当前紧凑值与标识符   enter_include1   联系起来,并被压入头文件的项中。头文件末尾的   pack   编译指示删除所有可能出现在头文件中的干预紧凑值,并且删除与   enter_include1   相关的紧凑值。因此确保该头文件的前后的紧凑值是相同的。

  这种功能也允许你使用代码,例如头文件,它可以使用   pack   编译指示设置不同于在你的代码中设置的紧凑值的紧凑对齐:
#pragma   pack(push,before_include1)
#include   "include1.h "
#pragma   pack(   pop,before_include1)
  在上面的例子中,对于出现在   include.h   中的紧凑值的任何变化,你的代码是受到保护的。

  • pragma是прагматика和практика的词根,意为行为、事情。 
       引申义有附注、标记,在这里就用来作为编译指示。
  • 希腊文,pragma,原意是“行动”、“实践”的意思. 
      
  • 这是以前我给人解答的 
       #pragma   
       是编译器的预处理指令,   各个编译器的用法不一样 
       #pragma   warning   (disable:4068) 
       是告诉编译器4068编号的警告就不要告诉我了 
       这样你编译室就不用看到一大堆警告 
       
       #pragma   hdrstop   [(   "filename"   )]     
       这个是用于指定头文件预编译的的文件名 
       用于缓存头文件的编译结果,   可以提高编译速度,   
       不用每次编译都要去编译一些没改动过的头文件,   很有用的; 
       具体VC中有stdafx.h   stdafx.cpp的一个重要作用就是这个 
       
       还有很多 
       #pragma   comment(   lib,   "emapi"   ) 
       比如这个就是告诉编译器我要用到emapi.lib这个库文件 
       #pragma   pack 
       这是对齐字节设置 
       
      
  • #pragma   
       预处理指令, 
       标准C++未定义其用途, 
       不同的编译器有不同的意思。

    文章来源: http://blog.csdn.net/jx_kingwei/archive/2005/04/28/367312.aspx

        在所有的预处理指令中,#pragma 指令可

  • 抱歉!评论已关闭.