14.3 条件编译
在编译一个程序时,如果可以翻译或忽略选定的某条语句或某组语句,常常会很方便。只用于调试程序的语句就是一个明显的例子。它们不应该出现在程序的产品版本中,但是我们可能并不想把这些语句从源代码中物理删除,因为在需要一些维护性修改时,可能需要重新调试这个程序,此时还需要这些语句。
条件编译(conditional compilation)可以实现这个目的。使用条件编译,可以选择代码的一部分是被正常编译还是完全忽略。用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令。下面显示了它最简单的语法形式:
#if constant-expression
statement
#endif
其中,constant-expression(常量表达式)由预处理器进行求值。如果它的值是非零值(真),那么statement部分就被正常编译,否则编译器就静默地删除它们。
所谓常量表达式,就是说它或者是字面值常量,或者是一个由#define定义的符号。如果变量在执行期之前无法获得它们的值,那么它们出现在常量表达式中就是非法的,因为它们的值在编译时是不可预知的。
例如,将所有的调试代码都以下面这种形式出现:
#if DEBUG
printf( "x=%d, y=%d\n", x, y );
#endif
这样,无论是想编译还是忽略这个代码,都很容易办到。如果想要编译它,只要使用
#define DEBUG 1
这个符号定义就可以了。如果想要忽略它,只要把这个符号定义为0就可以了。无论哪种情况,这段代码都可以保留在源文件中。
条件编译的另一个用途是在编译时选择不同的代码部分。为了支持这个功能,#if指令还具有可选的#elif和#else子句。完整的语法如下所示:
#if constant-expression
statement
#elif constant-expression
other-statement ...
#else
other statements
#endif
#elif子句出现的次数可以不限。每个constant-expression(常量表达式)只有当前面所有常量表达式的值都为假时才会被编译。#else子句中的语句只有当前面所有常量表达式的值都为假时才会被编译,在其他情况下都被会忽略。
K&R C:
最初的K&R C并没有#elif指令。但是,在这类编译器中,可以使用嵌套的指令来获得相同的效果。
下面这个例子取自一个以几个不同版本进行销售的程序。每个版本都有一组不同的选项特性。编写这个代码的困难在于如何让它产生不同的版本。必须避免为每个版本编写一组不同的源文件,因为这个代价太大了!由于各组源文件的绝大多数代码都是一样的,维护这个程序将成为一个噩梦。幸运的是,条件编译可以解决这个问题。
if( feature_selected == FEATURE1 )
#if FEATURE1_ENABLED_FULLY
feature1_function( arguments );
#elif FEATURE1_ENABLED_PARTIALLY
feature1_partial_function( arguments );
#else
printf( "To use this feature, send $39.95;"
" allow ten weeks for delivery.\n" );
#endif
/*
** 条件编译。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
#if !0
printf( "!0 is true!\n" );
#endif
#if 0
printf( "0 is false!\n" );
#endif
int x, y;
x = 1;
y = 2;
#undef DEBUG
#define DEBUG 0
#if DEBUG
printf( "DEBUG = %d, x = %d, y = %d\n", DEBUG, x, y );
#endif
#undef DEBUG
#define DEBUG 1
#if DEBUG
printf( "DEBUG = %d, x = %d, y = %d\n", DEBUG, x, y );
#endif
#undef DEBUG
#define DEBUG 5
#if DEBUG
printf( "DEBUG = %d, x = %d, y = %d\n", DEBUG, x, y );
#endif
#undef DEBUG
#define DEBUG 6
#if DEBUG
printf( "DEBUG = %d, x = %d, y = %d\n", DEBUG, x, y );
#endif
#undef ch
#define ch 'A'
#if ch == 'B'
printf( "ch == 'B'.\n" );
#elif ch == 'C'
printf( "ch == 'C'.\n" );
#elif ch == 'A'
printf( "ch == 'A'.\n" );
#else
printf( "I don't know what is ch equal to?\n" );
#endif
/*
** you can regard it as a reference.
if( feature_selected == FEATURE1 )
#if FEATURE1_ENABLED_FULLY
feature1_function( arguments );
#elif FEATURE1_ENABLED_PARTIALLY
feature1_partial_function( arguments );
#else
printf( "To use this feature, send $39.95;"
" allow ten weeks for delivery.\n" );
#endif
*/
return EXIT_SUCCESS;
}
/* 输出:

*/
14.3.1 是否被定义
测试一个符号是否已被定义也是可能的。在条件编译中完成这个任务往往更为方便,因为如果程序并不需要控制编译的符号所控制的特性,就不需要定义符号。这个测试可以通过下列任何一种方式进行:
#if defined( symbol )
#ifdef symbol
#if !defined( symbol )
#ifndef symbol
每对定义的两条语句是等价的,但#if形式功能更强。因为常量表达式可能包含额外的条件,如下所示:
#if X > 0 || defined( ABC ) && defined( BCD )
K&R C:
有些K&R C编译器可能并未包含所有这些功能,这取决于它们的年代如何久远。
14.3.1 是否被定义
测试一个符号是否已被定义也是可能的。在条件编译中完成这个任务往往更为方便,因为如果程序并不需要控制编译的符号所控
制的特性,就不需要定义符号。这个测试可以通过下列任何一种方式进行:
#if defined( symbol )
#ifdef symbol
#if !defined( symbol )
#ifndef symbol
每对定义的两条语句是等价的,但#if形式功能更强。因为常量表达式可能包含额外的条件,如下所示:
#if X > 0 || defined( ABC ) && defined( BCD )
K&R C:
有些K&R C编译器可能并未包含所有这些功能,这取决于它们的年代如何久远。
/*
** 是否被定义。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
#undef symbol
#define symbol 1
#if defined( symbol )
printf( "symbol = %d\n", symbol );
#endif
#ifdef symbol
printf( "symbol = %d\n", symbol );
#endif
#undef symbol
#if !defined( symbol )
printf( "symbol doesn't be defined.\n" );
#endif
#ifndef symbol
printf( "symbol doesn't be defined.\n" );
#endif
#undef X
#define X 0
#undef ABC
#define ABC 2
#undef BCD
#define BCD 0
/* the function of #if is stronger than #ifdef */
#if X > 0 || defined( ABC ) && defined( BCD )
printf( "X = %d, ABC = %d, BCD = %d\n", X, ABC, BCD );
#endif
return EXIT_SUCCESS;
}
/* 输出:

*/
14.3.2 嵌套指令
前面提高的这些指令可以嵌套于另一个指令内部,如下面的代码段所示:
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined( OS_MSDOS )
#ifdef OPTION2
msdos_version_of_option2();
#endif
#endif
在这个例子中,操作系统的选择将决定不同的选项可以使用那些方案。这个例子同时说明了预处理指令可以在它们前面添加空白,形成缩进,从而提高可读性。
为了帮助大家记住复杂的嵌套指令,可以为每个#endif加上一个注释标签,标签的内容就是#if(或#ifdef)后面的那个表达式。当#if(或#ifdef)和#endif之间的代码块非常长时,这种做法尤为有用。例如:
#ifdef OPTION1
lengthy code for option1;
#else
lengthy code for alternative;
#endif /* OPTION1 */
有些编译器允许一个符号出现于#endif指令中,它的作用和上面这种标签类似。不过这个符号对实际代码不会产生任何作用。标准并没有提及这种做法是否合法,所以更安全的做法还是使用注释。
/*
** 嵌套指令。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
#if defined( OS_UNIX )
#ifdef OPTION1
unix_version_of_option1();
#endif
#ifdef OPTION2
unix_version_of_option2();
#endif
#elif defined( OS_MSDOS )
#ifdef OPTION2
msdos_version_of_option2();
#endif
#endif
#ifdef OPTION1
/* lengthy code for option1; */
#else
/* lengthy code for alternative; */
#endif /* OPTION1 */
return EXIT_SUCCESS;
}
/* 输出:

*/