当前位置:嗨网首页>书籍在线阅读

17-条件编译

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

16.6.3 条件编译

可以使用其他指令创建条件编译(conditinal compilation)。也就是说,可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)块。

1. #ifdef#else#endif 指令

我们用一个简短的示例来演示条件编译的情况。考虑下面的代码:

#ifdef MAVIS
   #include "horse.h" // 如果已经用#define定义了 MAVIS,则执行下面的指令
   #define STABLES 5
#else
   #include "cow.h"  //如果没有用#define定义 MAVIS,则执行下面的指令
   #define STABLES 15
#endif

这里使用的较新的编译器和ANSI标准支持的缩进格式。如果使用旧的编译器,必须左对齐所有的指令或至少左对齐 # 号,如下所示:

#ifdef MAVIS
#include "horse.h"  // 如果已经用#define定义了 MAVIS,则执行下面的指令
#define STABLES 5
#else
#include "cow.h"   //如果没有用#define定义 MAVIS,则执行下面的指令
#define STABLES 15
#endif

#ifdef 指令说明,如果预处理器已定义了后面的标识符( MAVIS ),则执行 #else#endif 指令之前的所有指令并编译所有C代码(先出现哪个指令就执行到哪里)。如果预处理器未定义 MAVIS ,且有 #else 指令,则执行 #else#endif 指令之间的所有代码。

#ifdef   #else 很像C的 if   else 。两者的主要区别是,预处理器不识别用于标记块的花括号( {} ),因此它使用 #else (如果需要)和 #endif (必须存在)来标记指令块。这些指令结构可以嵌套。也可以用这些指令标记C语句块,如程序清单16.9所示。

程序清单16.9  ifdef.c 程序

/* ifdef.c -- 使用条件编译 */
#include <stdio.h>
#define JUST_CHECKING
#define LIMIT 4
int main(void)
{
   int i;
   int total = 0;
   for (i = 1; i <= LIMIT; i++)
   {
     total += 2 * i*i + 1;
#ifdef JUST_CHECKING
     printf("i=%d, running total = %d\n", i, total);
#endif
   }
   printf("Grand total = %d\n", total);
   return 0;
}

编译并运行该程序后,输出如下:

i=1, running total = 3
i=2, running total = 12
i=3, running total = 31
i=4, running total = 64
Grand total = 64

如果省略 JUST_CHECKING 定义(把它放在C注释中,或者使用 #undef 指令取消它的定义)并重新编译该程序,只会输出最后一行。可以用这种方法来调试程序。定义 JUST_CHECKING 并合理使用 #ifdef ,编译器将执行用于调试的程序代码,打印中间值。调试结束后,可移除 JUST_CHECKING 定义并重新编译。如果以后还需要使用这些信息,重新插入定义即可。这样做省去了再次输入额外打印语句的麻烦。 #ifdef 还可用于根据不同的C实现选择合适的代码块。

2. #ifndef 指令

#ifndef 指令与 #ifdef 指令的用法类似,也可以和 #else#endif 一起使用,但是它们的逻辑相反。 #ifndef 指令判断后面的标识符是否是未定义的,常用于定义之前未定义的常量。如下所示:

/* arrays.h */
#ifndef SIZE
   #define SIZE 100
#endif

(旧的实现可能不允许使用缩进的 #define

通常,包含多个头文件时,其中的文件可能包含了相同宏定义。 #ifndef 指令可以防止相同的宏被重复定义。在首次定义一个宏的头文件中用 #ifndef 指令激活定义,随后在其他头文件中的定义都被忽略。

#ifndef 指令还有另一种用法。假设有上面的 arrays.h 头文件,然后把下面一行代码放入一个头文件中:

#include "arrays.h"

SIZE 被定义为 100 。但是,如果把下面的代码放入该头文件:

#define SIZE 10
#include "arrays.h"

SIZE 则被设置为 10 。这里,当执行到 #include "arrays.h" 这行,处理 array.h 中的代码时,由于 SIZE 是已定义的,所以跳过了 #define SIZE 100 这行代码。鉴于此,可以利用这种方法,用一个较小的数组测试程序。测试完毕后,移除 #define SIZE 10 并重新编译。这样,就不用修改头文件数组本身了。

#ifndef 指令通常用于防止多次包含一个文件。也就是说,应该像下面这样设置头文件:

/* things.h */
#ifndef THINGS_H_
   #define THINGS_H_
   /* 省略了头文件中的其他内容*/
#endif

假设该文件被包含了多次。当预处理器首次发现该文件被包含时, THINGS_H_ 是未定义的,所以定义了 THINGS_H_ ,并接着处理该文件的其他部分。当预处理器第2次发现该文件被包含时, THINGS_H_ 是已定义的,所以预处理器跳过了该文件的其他部分。

为何要多次包含一个文件?最常见的原因是,许多被包含的文件中都包含着其他文件,所以显式包含的文件中可能包含着已经包含的其他文件。这有什么问题?在被包含的文件中有某些项(如,一些结构类型的声明)只能在一个文件中出现一次。C标准头文件使用 #ifndef 技巧避免重复包含。但是,这存在一个问题:如何确保待测试的标识符没有在别处定义。通常,实现的供应商使用这些方法解决这个问题:用文件名作为标识符、使用大写字母、用下划线字符代替文件名中的点字符、用下划线字符做前缀或后缀(可能使用两条下划线)。例如,查看 stdio.h 头文件,可以发现许多类似的代码:

#ifndef _STDIO_H
#define _STDIO_H
// 省略了文件的内容
#endif

你也可以这样做。但是,由于标准保留使用下划线作为前缀,所以在自己的代码中不要这样写,避免与标准头文件中的宏发生冲突。程序清单16.10修改了程序清单16.6中的头文件,使用 #ifndef 避免文件被重复包含。

程序清单16.10  names.h 程序

// names.h --修订后的 names_st 头文件,避免重复包含
#ifndef NAMES_H_
#define NAMES_H_
// 明示常量
#define SLEN 32
// 结构声明
struct names_st
{
   char first[SLEN];
   char last[SLEN];
};
// 类型定义
typedef struct names_st names;
// 函数原型
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st, int n);
#endif

用程序清单16.11的程序测试该头文件没问题,但是如果把清单16.10中的 #ifndef 保护删除后,程序就无法通过编译。

程序清单16.11  doubincl.c 程序

// doubincl.c -- 包含头文件两次
#include <stdio.h>
#include "names.h"
#include "names.h"  // 不小心第2次包含头文件
int main()
{
   names winner = { "Less", "Ismoor" };
   printf("The winner is %s %s.\n", winner.first,
     winner.last);
   return 0;
}

3. #if#elif 指令

#if 指令很像C语言中的 if#if 后面跟整型常量表达式,如果表达式为非零,则表达式为真。可以在指令中使用C的关系运算符和逻辑运算符:

#if SYS == 1
#include "ibm.h"
#endif

可以按照 if else 的形式使用 #elif (早期的实现不支持 #elif )。例如,可以这样写:

#if SYS == 1
   #include "ibmpc.h"
#elif SYS == 2
   #include "vax.h"
#elif SYS == 3
   #include "mac.h"
#else
   #include "general.h"
#endif

较新的编译器提供另一种方法测试名称是否已定义,即用 #if defined (VAX) 代替 #ifdef VAX

这里, defined 是一个预处理运算符,如果它的参数是用 #defined 定义过,则返回 1 ;否则返回 0 。这种新方法的优点是,它可以和 #elif 一起使用。下面用这种形式重写前面的示例:

#if defined (IBMPC)
   #include "ibmpc.h"
#elif defined (VAX)
   #include "vax.h"
#elif defined (MAC)
   #include "mac.h"
#else
   #include "general.h"
#endif

如果在VAX机上运行这几行代码,那么应该在文件前面用下面的代码定义 VAX

#define VAX

条件编译还有一个用途是让程序更容易移植。改变文件开头部分的几个关键的定义,即可根据不同的系统设置不同的值和包含不同的文件。