编译一个C程序涉及很多步骤。其中第1步被称为预处理。C预处理器在源代码编译之前进行一些文本性质的操作。它的主要任务包括删除注释、插入被#include指令包含的文件内容、定义和替换由#define指令定义的符号等。
C和C++中都使用#include 指令包含头文件。#include 支持包含函数库文件和本地文件。
我们在写一个小程序的时候,大多数情况下不会遇到这种嵌套包含的问题。但有些同学尝试使用多个头文件时,常会遇到“与‘**’类型冲突”的错误,最后不得将多个头文件合成一个。有些同学还会奇怪的问,为什么函数库头文件可以多次包含,而我的就不行呢?
让我们假设下面这种情况是一种比较简单的嵌套头文件包含情况:
a.h文件有一个结构体声明:mystruct
b.h 使用#include “a.h" 包含了a.h ,同时定义了一个函数print_b()
c.h 使用#include “a.h" 包含了a.h ,同时定义了一个函数print_c()
d.c 同时包含了 b.h 和c.h
关系如下图:
a.h
#include<stdlib.h> #include<stdio.h> #include<string.h> typedef struct mystruct{ char a[10]; }mystruct;
b.h
#include "a.h" void print_b(){ mystruct a; strcpy(a.a,"a.h"); printf("this is in b.h %s\n",a.a); }
#include "a.h" void print_c(){ mystruct c; strcpy(c.a,"a.h"); printf("this is in c.h %s\n",c.a); }
#include "b.h" #include "c.h" int main(){ printf("this is in main\n"); print_b(); print_c(); return 0; }
gcc a.h b.h c.h d.c -o d.o 不能编译成功,有错误
# gcc a.h b.h c.h d.c -o d.o In file included from c.h:1:0, from d.c:2: a.h:4:16: 错误: ‘struct mystruct’重定义 a.h:4:16: 附注: 原先在这里定义 a.h:6:2: 错误: 与‘mystruct’类型冲突 a.h:6:2: 附注: ‘mystruct’的上一个声明在此
原因是,预处理的时候,会读入整个头文件的内容,a.h的内容被重复包含了两次。出现了类型冲突。
在编译一个程序的时候,使用条件编译,你可以选择代码的一部分(或全部)是被正常编译还是完全忽略。这里,它就可以很好的派上用场。
替换a.h为下面内容
a.h
/*a.h*/ #include<stdlib.h> #include<stdio.h> #include<string.h> #ifndef _a_H #define _a_H 1 typedef struct mystruct{ char a[10]; }mystruct; #endif
./d.o
this is in main this is in b.h a.h this is in c.h a.h
发现并没有类型冲突。
其实就是在a.h中使用了条件编译(这也是函数库头文件可以多次包含的原因)。
#ifndef _a_H
#define _a_H 1
/*这是是你头文件本来的内容*/
#endif
这样多次重复包含的危险就被消除了。当头文件第一次被包含的时候,它正常处理,符号 _a_H( 可以自己定义符号 )被定义为 1(这个不是必须的,也可以直接写成#define _a_H)。如果头文件再次被包含时,通过条件编译,它说包含(#Iinfdef ...#endif 之间)的内容就会被忽略。
这里的示例只是为了更清楚的看到条件编译的作用,所以只在a.h当中使用了,其实如果我们所有头文件都是要上述的条件编译的方法编写,就可以消除多次包含的危险。因为其他人使用你的头文件的时候,可能并不清楚它已经包含了多少其他文件。(最好能够尽避免多次包含,因为预处理仍会读入整个头文件,即使它已经被忽略)
当然条件编译的作用不仅仅于此,比如在编写程序的时候需要debug的时候要经常用到。一些debug在编写程序阶段需要,当交付是有不需要让他们输出,这时候条件编译就大有用处。(下面是我常用的,注意这是使用的是#ifdef)
/*#define DEBUG_MIGRATION */ #ifdef DEBUG_MIGRATION #ifdef CONFIG_LINUX #include <sys/syscall.h> #define DPRINTF(fmt, ...) \ do { \ printf("%d:%ld %s:%d: " fmt, getpid(), syscall(SYS_gettid), \ __func__, __LINE__, ## __VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) \ do { \ printf("%s:%d: " fmt, __func__, __LINE__, ## __VA_ARGS__); \ } while (0) #endif #else #define DPRINTF(fmt, ...) do { } while (0) #endif
需要输出debug的时候就#define DEBUG_MIGRATION ,不需要的时候就注释它,那么整个文件的debug信息就不会输出。
下面是使用的范例:
DPRINTF("flushed %zu of %zu byte(s)\n", offset, s->buffer_size);需要输出debug的地方就使用DPRINTF。
常用的语法形式如下:
#if constant-expression statements #elseif constant-expression statements2 #else other statements #endif
常用的判断是否被定义的有
#ifndef something/* something 没有被定义*/
#ifdef something/*something 已经被定义*/
参考:
pointers on c, Kenneth A.Reek
QEMU/KVM源码