我们在平时的项目中,难免会遇到这样的问题:一个产品需要好几个版本(如低、中、高版本)。那么问题来了,我们需要对这一个产品进行几个版本的人马的同时开发吗?当然是不用啦,企业是讲究效益的,当然是希望一个产品一份代码就搞定啦。在这时我们就可以使用 C 语言中的条件编译啦,它会使同一份代码可以产生不同个版本的产品。下来我们来介绍下条件编译。
条件编译的行为类似于 C 语言中的if ... else ...,条件编译是预编译指示命令用于控制是否编译某段代码。我们通过下面这个示例代码进行分析,代码如下#include
#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
#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 ""
# 1 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "" 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 ""
# 1 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "" 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
#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。