预编译
在C语言源代码里,除了那些被注释了的代码不会被编译进程序里外,还有一种方式来规定代码是否会被编译进程序里,这就是所谓的条件编译,即代码只有符合某种条件下才能编译进程序。因为有的代码只有在某些情况下才具有被执行的可能,在其它条件下这些代码要么无法执行,要么没有必要执行。比如在Windows平台,有的函数只能在Vista以上平台才能被执行,那么在XP及以下的平台里就无法被执行,因此在XP及以下平台就没有必要将这部分代码编译进程序,有时候甚至还无法在这些平台里编译通过。又比如有的代码只能在X86平台才能执行,在X64平台下是无效的,因此也要对这部分代码进行条件编译。
条件编译可以有多种定义形式。这些定义形式详细介绍如下。
13.5.1 方式1
#ifdef 标识符
程序段1
#else
程序段2
#endif
在上面这种定义形式下,当标识符被定义(通过#define),那么就将程序段1编译进程序,否则,就将程序段2编译进程序。
#define WINVER 6.1
int main(void)
{
#ifdef WINVER
/*1*/ printf(“wenversion defined\n”);
#else
/*2*/ printf(“winversion not defined\n”);
#endif
return 0;
}
上面,通过宏定义将WINVER进行了定义,那么语句1而不是语句2会被编译进程序。在对WINVER进行定义的时候,即使后面不跟任何字符串(比如这里的6.1),也会被当作定义了。比如:
#define WINVER
int main(void)
{
#ifdef WINVER
/*1*/ printf(“wenversion defined\n”);
#else
/*2*/ printf(“winversion not defined\n”);
#endif
return 0;
}
这样,语句1依然会被编译进程序,而会排除语句2编译进程序。
13.5.2 方式2
#ifndef 标识符
程序段1
#else
程序段2
#endif
在方式2的定义下,如果标识符没有定义,那么程序段1会被编译进程序,否则程序段2会被编译进程序。
#define WINVER 6.1
int main(void)
{
#ifndef WINVER
/*1*/ printf(“wenversion defined\n”);
#else
/*2*/ printf(“winversion not defined\n”);
#endif
return 0;
}
同样以上面的程序为例子,在方式2的情况下,如果定义了WINVER,那么语句2会被编译进程序。因为前面的宏定义已经将WINVER进行了定义。
13.5.3 方式3
#if 常量表达式
程序段1
#else
程序段2
#endif
在定义方式3中,如果常量表达式的值为真,那么程序段1将会被编译进程序,否则,程序段2会被编译进程序。
#define DEBUG 1
int main(void)
{
int a = 5;
int b = 10;
int c = a + b;
#if DEBUG
/*1*/ printf(“c
= %d\n”, c);
#endif
}
在定义了DEBUG为1的情况下,那么程序将会把语句1编译进程序,将a+b的结果在调试情况下打印输出。而一旦将DEBUG定义为0的情况下,语句1就无法编译进程序了。比如:
#define DEBUG 0
int main(void)
{
int a = 5;
int b = 10;
int c = a + b;
#if DEBUG
/*1*/ printf(“c = %d\n”, c); //此句不会被编译进程序
#endif
return 0;
}
也可以写为:
int main(void)
{
int a = 5;
int b = 10;
int c = a + b;
#if 0
/*1*/ printf(“c = %d\n”, c); //此句不会被编译进程序
#endif
return 0;
}
13.5.4 方式4
#if 表达式1
语句段1
#elif 表达式2
语句段2
#else
语句段3
#endif
#define X64
int main(void)
{
#if defined(X64)
printf(“x64 platform specific\n”);
#elif defined(X86)
printf(“x86 platform specific\n”);
#else
printf(“common\n”);
#endif
return 0;
}
其中defined()用来判断一个标识符是否被定义,如果定义返回为真,否则返回假。而加上个!运算符则表示取反。此外,#undef用来取消对一个标识符的定义,比如:
#ifdef A
#undef A
#endif
上面的条件编译语句,就是在A被定义的情况下取消对A的定义。这个和在头文件的写法中的方式正好相反。
现在来看在MS的库中,对LARGE_INTEGER这个64位大整数的定义,就使用了条件编译:
#if defined(MIDL_PASS)
typedef struct _LARGE_INTEGER {
#else // MIDL_PASS
typedef union _LARGE_INTEGER {
struct {
ULONG LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
ULONG LowPart;
LONG HighPart;
} u;
#endif //MIDL_PASS
LONGLONG QuadPart;
} LARGE_INTEGER;
一旦定义了MIDL_PASS,那么LARGE_INTEGER将会被定义为:
typedef struct _LARGE_INTEGER {
LONGLONG QuadPart;
} LARGE_INTEGER;
否则会被定义为:
typedef union _LARGE_INTEGER {
struct {
ULONG LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
ULONG LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
下面是一个实际工程中使用的一个条件编译的例子:
#if WINVER >= 0x0501
//
// Try to load the dynamic functions that may be available for our use.
//
SfLoadDynamicFunctions();
//
// Now get the current OS version that we will use to determine what logic
// paths to take when this driver is built to run on various OS version.
//
SfGetCurrentVersion();
#endif
#if DBG && WINVER >= 0x0501
//
// MULTIVERSION NOTE:
//
// We can only support unload for testing environments if we can enumerate
// the outstanding device objects that our driver has.
//
//
// Unload is useful for development purposes. It is not recommended for
// production versions
//
if (NULL != gSfDynamicFunctions.EnumerateDeviceObjectList) {
gSFilterDriverObject->DriverUnload = DriverUnload;
}
#endif
注意到,条件编译中提到的语句段,并不一定是一条完整的语句,比如头文件定义方式使用的其实也是条件编译。
13.6 头文件定义
#ifndef _FILENAME_H_
#define _FILENAME_H_
#ifdef __cplusplus
extern "C" {
#endif
…
…
...
...
...
...
#ifdef __cplusplus
}
#endif
#endif /*
_FILENAME_H_ */
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif