C 语言条件编译简介

目录汇总:C 语言零基础入门教程

C 语言的预处理器可以识别大量用于支持条件编译的指令。条件编译是指根据预处理器所执行的测试结果来包含或排除程序的片段。

一、#if 指令和 #endif 指令

假如我们正在调试一个程序。我们想要程序显示出特定变量的值,因此将 printf 函数调用添加到程序中重要的部分。一旦找到错误,建议保留这些 printf 函数调用,以备后用。条件编译允许我们保留这些调用,但是让编译器忽略它们。

下面是我们需要采取的方式。首先定义一个宏,并给它一个非零的值:

1
#define DEBUG 1

宏的名字并不重要。接下来,我们要在每组 printf 函数调用的前后加上 #if#endif

1
2
3
4
#if DEBUG
printf("Value of i: %d\n", i);
printf("Value of j: %d\n", j);
#endif

在预处理过程中,#if 指令会测试 DEBUG 的值。由于 DEBUG 的值不是 0,因此预处理器会将这两个 printf 函数调用保留在程序中(但 #if#endif 行会消失)。如果我们将 DEBUG 的值改为 0 并重新编译程序,预处理器则会将这 4 行代码都删除。编译器不会看到这些 printf 函数调用,所以这些调用就不会在目标代码中占用空间,也不会在程序运行时消耗时间。我们可以将 #if-#endif 保留在最终的程序中,这样如果程序在运行时出现问题,可以(通过将 DEBUG 改为 1 并重新编译来)继续产生诊断信息。

一般来说,#if 指令的格式如下:

1
[#if指令] #if 常量表达式

#endif 指令则更简单:

1
[#endif指令] #endif

当预处理器遇到 #if 指令时,会计算常量表达式的值。如果表达式的值为 0,那么 #if#endif 之间的行将在预处理过程中从程序中删除;否则,#if#endif 之间的行会被保留在程序中,继续留给编译器处理——这时 #if#endif 对程序没有任何影响。

值得注意的是,#if 指令会把没有定义过的标识符当作值为 0 的宏对待。因此,如果省略 DEBUG 的定义,测试

1
#if DEBUG

会失败(但不会产生出错消息),而测试

1
#if !DEBUG

会成功。

二、defined 运算符

C 语言宏定义简介 中介绍过运算符 ###,还有一个专用于预处理器的运算符——defined。当 defined 应用于标识符时,如果标识符是一个定义过的宏则返回 1,否则返回 0。defined 运算符通常与 #if 指令结合使用,可以这样写:

1
2
3
#if defined(DEBUG)
...
#endif

仅当 DEBUG 被定义成宏时,#if#endif 之间的代码会被保留在程序中。DEBUG 两侧的括号不是必需的,因此可以简单地写成

1
#if defined DEBUG

因为 defined 运算符仅检测 DEBUG 是否有定义,所以不需要给 DEBUG 赋值:

1
#define DEBUG

三、#ifdef 指令和 #ifndef 指令

#ifdef 指令测试一个标识符是否已经定义为宏:

1
[#ifdef指令] #ifdef 标识符

#ifdef 指令的使用与 #if 指令类似:

1
2
3
#ifdef 标识符
当标识符被定义为宏时需要包含的代码
#endif

严格地说,并不需要 #ifdef,因为可以结合 #if 指令和 defined 运算符来得到相同的效果。换言之,指令

#ifdef 标识符

等价于

#if defined(标识符)

#ifndef 指令与 #ifdef 指令类似,但测试的是标识符是否没有被定义为宏:

1
[#ifndef指令] #ifndef 标识符

指令

#ifndef 标识符

等价于指令

#if !defined(标识符)

四、#elif 指令和 #else 指令

#if 指令、#ifdef 指令和 #ifndef 指令可以像普通的 if 语句那样嵌套使用。当发生嵌套时,最好随着嵌套层次的增加而增加缩进。一些程序员对每一个 #endif 都加注释,来指明对应的 #if 指令测试哪个条件:

1
2
3
#if DEBUG
...
#endif /* DEBUG */

这种方法有助于更方便地找到 #if 指令的起始位置。

为了提供更多的便利,预处理器还支持 #elif#else 指令:

1
2
3
[#elif指令] #elif 常量表达式

[#else指令] #else

#elif 指令和 #else 指令可以与 #if 指令、#ifdef 指令和 #ifndef 指令结合使用,来测试一系列条件:

1
2
3
4
5
6
7
#if 表达式1
当表达式10时需要包含的代码
#elif 表达式2
当表达式10但表达式20时需要包含的代码
#else
其他情况下需要包含的代码
#endif

虽然上面的例子使用了 #if 指令,但 #ifdef 指令或 #ifndef 指令也可以这样使用。在 #if 指令和 #endif 指令之间可以有任意多个 #elif 指令,但最多只能有一个 #else 指令。

五、使用条件编译

条件编译对于调试是非常方便的,但它的应用并不仅限于此。下面是其他一些常见的应用。

  • 编写在多台机器或多种操作系统之间可移植的程序。下面的例子中会根据 WIN32、MAC_OS 或 LINUX 是否被定义为宏,而将三组代码之一包含到程序中:

    1
    2
    3
    4
    5
    6
    7
    
    #if defined(WIN32)
    ...
    #elif defined(MAC_OS)
    ...
    #elif defined(LINUX)
    ...
    #endif
    

    一个程序中可以包含许多这样的 #if 指令。在程序的开头会定义这些宏之一(而且只有一个),由此选择了一个特定的操作系统。例如,定义 LINUX 宏可以指明程序将运行在 Linux 操作系统下。

  • 编写可以用不同的编译器编译的程序。不同的编译器可以用于识别不同的 C 语言版本,这些版本之间会有一些差异。一些会接受标准 C,另一些则不会。一些版本会提供针对特定机器的语言扩展;另一些版本则没有,或者提供不同的扩展集。条件编译可以使程序适应于不同的编译器。考虑一下为以前的非标准编译器编写程序的问题。__STDC__ 宏允许预处理器检测编译器是否支持标准(C89 或 C99)。如果不支持,我们可能必须修改程序的某些方面,尤其是有可能必须用老式的函数声明(见 C 语言函数:问与答)替代函数原型。对于每一处函数声明,我们可以使用下面的代码:

    1
    2
    3
    4
    5
    
    #if __STDC__
    函数原型
    #else
    老式的函数声明
    #endif
    
  • 为宏提供默认定义。条件编译使我们可以检测一个宏当前是否已经被定义了,如果没有,则提供一个默认的定义。例如,如果宏 BUFFER_SIZE 此前没有被定义的话,下面的代码会给出定义:

    1
    2
    3
    
    #ifndef BUFFER_SIZE
    #define BUFFER_SIZE 256
    #endif
    
  • 临时屏蔽包含注释的代码。我们不能用 /*...*/ 直接“注释掉”已经包含 /*...*/ 注释的代码。然而,我们可以使用 #if 指令来实现:

    1
    2
    3
    
    #if 0
    包含注释的代码行
    #endif
    

    将代码以这种方式屏蔽,经常称为“条件屏蔽”。

请参阅

(完)

comments powered by Disqus