关于预处理指令的困惑:头文件内容如何正确引入child.c?
头文件卫士如何确保内容正确引入且不重复定义?
先看你给出的三个文件内容:
grandparent.h
#ifndef GRANDPARENT_H #define GRANDPARENT_H struct foo { int member; }; #endif /* GRANDPARENT_H */
parent.h
#include "grandparent.h"
child.c
#include "grandparent.h" #include "parent.h"
你的疑问点在于:既然头文件卫士靠GRANDPARENT_H宏来判断,那第一次引入时,宏和结构体内容是怎么一起被放进child.c的?其实核心是预处理器处理头文件卫士的逻辑是「先判断,再执行块内所有内容」,我给你拆解child.c的预编译全过程:
第一次处理
#include "grandparent.h"
预处理器看到这个include,会直接把grandparent.h的内容拉到child.c里。此时GRANDPARENT_H还没被定义,所以#ifndef GRANDPARENT_H的判断结果是真——预处理器会把#ifndef到#endif之间的所有代码都保留并插入到child.c中,包括:#define GRANDPARENT_H(把这个宏标记为已定义)struct foo { int member; };(结构体定义)
这一步完成后,
child.c里已经有了struct foo的完整定义,同时GRANDPARENT_H宏也被标记为已存在。处理
#include "parent.h"
预处理器把parent.h的内容拉进来,发现里面又是#include "grandparent.h",于是再次处理这个嵌套的include。
这时候检查GRANDPARENT_H,发现它已经被定义了,所以#ifndef GRANDPARENT_H的判断结果是假——预处理器会直接跳过#ifndef到#endif之间的所有内容,既不会重复定义GRANDPARENT_H,也不会再次插入struct foo的定义,完美避免了重复定义问题。
简单来说,头文件卫士就像一扇带锁的门:
- 第一次推门(include)时,门没锁,你不仅能把屋里的结构体(内容)拿出来,还会顺手把门锁上(定义宏);
- 之后再推门(重复include)时,门已经锁死了,你拿不到屋里的东西,自然不会重复拿结构体定义,但第一次已经把结构体拿到手了,所以代码里已经有了完整的定义,能正常编译。
内容的提问来源于stack exchange,提问作者rooni




