条件编译的行为类似于 C 语言中的 if ... else ... ,条件编译是预编译指示命令用于控制是否编译某段代码。我们通过下面这个示例代码进行分析,代码如下
#include <stdio.h> #define C 1 int main() { const char* s; #if ( C == 1 ) s = "This is first printf...\n"; #else s = "This is second printf...\n"; #endif printf("%s", s); return 0; }
我们来分析下,如果我们直接编译的话,就会打印 This is first printf... 这句话。编译后看看结果
结果如我们分析的那样,下来我们注释掉第3行的宏定义,那么就会打印 This is second printf... 这句话了,这个就不做实验了,大家可以自己做下看看结果是否如我们分析的那样。我们在上面说到 条件编译的行为类似于 C 语言中的看代码的话确实比较像,我们再来单步编译下,看看代码是如何被处理的,为了避免出现不相干的代码 if ... else ... ,我们注释掉头文件和打印语句,结果如下
大家可以看到它直接就被替换了。下来我们就讲下如何在宏定义被注释掉的情况下还打印出第一句话,那么这就是我们所谓的条件编译了。
预编译器根据条件编译指令有选择的删除代码,编译器不知道代码分支的存在。 if ... else ... 语句在运行期进行分支判断,而条件编译指令在预编译期进行分支判断。我们可以通过命令行宏定义来指定条件编译,命令如:gcc -Dmacro=value file.c 或 gcc -Dmacro file.c。ps:因为宏定义在 C 语言中还有个起到标识符的作用,因此我们可以直接命令行定义它,使条件为真即可。
我们将上面的代码中的第3行的宏定义去掉,再将第9行的 #if ( C == 1 ) 变成 #ifden C,再来编译下,看看结果
我们再次加上 -Dmacro 选项,编译结果如下
我们看到通过在命令行定义宏来达到条件编译的结果。下面我们来讲讲 #include 的本质,它的本质是将已经存在的文件内容嵌入到当前文件中,#include 的间接包含同样会产生嵌入文件内容的操作。我们看看间接包含同一个头文件是否会产生编译错误,结构如下
我们来创建 global.h 和 test.h。
global.h 代码
// global.h int global = 10;
test.h 代码
// test.h #include "global.h" const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; }
test.c 代码
#include <stdio.h> #include "test.h" #include "global.h" int main() { const char* s = hello_world(); int g = global; printf("%s\n", NAME); printf("%d\n", g); return 0; }
我们来看看编译结果
它说 global 重复定义了,我们再来仔细看看三个文件,发现只是在 global.h 文件中定义了 global = 10,那么这是怎么回事呢?我们来单步编译下,看看代码是什么样的(同样注释掉头文件和打印语句),由于代码过长,所以就直接上代码了,代码如下
# 1 "test.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "test.c" # 1 "test.h" 1 # 1 "global.h" 1 int global = 10; # 4 "test.h" 2 const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; } # 3 "test.c" 2 # 1 "global.h" 1 int global = 10; # 4 "test.c" 2 int main() { const char* s = hello_world(); int g = global; return 0; }
我们发现在第 14 行定义了 global,在后面包含 global.h 时又重新定义了 global 变量。这样看来编译器报的错误就很正常了,那么我们经常会使用到包含多个头文件,也没见会发生重复定义啊。这时利用条件编译便可以解决头文件重复包含的编译错误,格式如下
#ifndef _HEADER_FILE_H_ #define _HEADER_FILE_H_ // source code #endif
我们再次在两个头文件中分别加上条件编译,我们再次编译,结果如下
没有报错,而是直接成功执行,我们再来单步编译下,看看代码是怎样的
# 1 "test.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "test.c" # 1 "test.h" 1 # 1 "global.h" 1 int global = 10; # 6 "test.h" 2 const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; } # 3 "test.c" 2 int main() { const char* s = hello_world(); int g = global; return 0; }
我们发现 global 变量只定义了一次。这便是我们条件编译的一种应用啦。那么条件编译的意义还不止这些,还有如下几点:a> 条件编译使得我们可以按不同的条件编译出不同的代码段,因而可以产生不同的目标代码;b> #if ... #else .. #endif 被预编译器处理,而 if ... else... 语句被编译器处理,必然被编译进目标代码。在实际的工程中条件编译主要用于以下两种情况:1. 不同的产品线共用一份代码;2. 区分编译产品的调试版和发布版。我们看看下面的示例代码
#include <stdio.h> #include "product.h" #if DEBUG #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s) #else #define LOG(s) NULL #endif #if HIGH void f() { printf("This is the high level product!\n"); } #else void f() { } #endif int main() { LOG("Enter main() ..."); f(); printf("1. Query Information.\n"); printf("2. Record Information.\n"); printf("3. Delete Information.\n"); #if HIGH printf("4. High Level Query.\n"); printf("5. Mannul Service.\n"); printf("6. Exit.\n"); #else printf("4. Exit.\n"); #endif LOG("Exit main() ..."); return 0; }
product.h 代码如下
#define DEBUG 1 #define HIGH 1
我们分析下,如果定义 DEBUG 的话,我们便定义 LOG 日志宏,以便来打印一些信息。如果定义 HIGH 的话,我们便是高版本的了,在 main 函数中除了打印 1 2 3,还将会打印出 4 5 6。如果没定义 HIGH,便打印 4 就完了。因为我么在 product.h 中分别定义它们为真,所以应该打印出的是高版本的调试版的。我们看看编译结果
结果如我们分析的那样,我们如果需要一个低版本的发布版,这时只需要在 product.h 中定义 DEBUG 和 HIGH 分别为 0,我们看看结果
我们如果需要一个高版本的发布版,这时只需要在 product.h 中定义 DEBUG 为 0 和 HIGH 为 1,结果如下
那么我们这时就可以根据我们的需求来编译各种版本的啦。通过对条件编译的学习,总结如下:1、通过编译器命令行能够定义预处理器使用的宏;2、条件编译可以避免重复包含同一个头文件;3、条件编译是在工程开发中可以区别不同产品线的代码;4、条件编译可以定义产品的发布版和调试版。
欢迎大家一起来学习 C 语言,可以加我QQ:243343083。
原文:http://blog.51cto.com/12810168/2103372