include 嵌套 重复 文件包含使用条件编译处理

          编译一个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);
}

c.h

#include "a.h"
void print_c(){
    mystruct c;
    strcpy(c.a,"a.h");
    printf("this is in c.h %s\n",c.a);
}

d.c

#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


gcc a.h b.h c.h d.c -o d.o

./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源码


  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值