信心比金钱更重要!

目标明确==>>>计划跟踪==>>>行动执行!
posts - 41, comments - 3, trackbacks - 0, articles - 2
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

关于使用ifndef避免宏重复定义

Posted on 2012-04-26 15:49 luofeng 阅读(467) 评论(0)  编辑 收藏 引用 所属分类: C语言

http://blog.chinaunix.net/uid-22275351-id-10905.html


1. 多次包含的情况

include xxx 就是将xxx的内容原地展开

假设有:
a.h, 内容是A

b.h, 内容是:
#include "a.h"
B

c.h, 内容是:
#include "a.h"
C

如果有一个文件x.c, 内容是:
#include "b.h"
#include "c.h"
X

b.h和c.h的内容就会被插入到X之前, 也就是这个样子:
A
B
A
C
X

A的内容就出现了2次。

在更复杂的环境中, A的内容还可能出现多次。


2. 多次出现是有问题的

一般来说, 重复声明没什么问题。
所以, 如果A.h中止包含一些声明, 那重复了也没什么关系。

比如:
  1. int f(int);
  2. int f(int);
  3. int f(int);
  4. extern int i;
  5. extern int i;
  6. extern int i;
  7. struct x;
  8. struct x;
  9. struct x;
复制代码
重复写N次也没关系。

但头文件中会出现一类"定义", 在同一翻译单元中是不能重复的。
比如:
  1. struct x { ... };
  2. struct x { ... }; // 重复定义

  3. #define M ...
  4. #define M ...  // 重复定义
复制代码
3. 头文件保护符

有时候必须将这些定义放在头文件中, 所以就要用头文件保护符。
另外还有一类"定义", 会产生外部符号。
这类"定义"在一个链接过程中只能有唯一一份。
是不可以加入到头文件中的。
这种定义依然有例外……  就是inline、模板和匿名名字空间, 就不扯远了……


假设A的内容是:
  1. #ifndef A_H
  2. #define A_H
  3. AA
  4. #endif
复制代码
如果A被展开多次,例如上面的X, 就会变成这个样子
  1. // A_H是a.h的保护符, 必须是一个不冲突的名字。 那么,这里就不会有A_H的定义
  2. // 然后紧接这下一行中的条件编译就会选中#ifndef 和#endif之间的部分, 也就是#define A_H 和AA
  3. #ifndef A_H
  4. #define A_H
  5. AA
  6. #endif


  7. B

  8. // 在a.h被第一次包含后, A_H就获得定义
  9. // 所以下一行的条件编译部分就被取消, AA就不会重复出现多次
  10. #ifndef A_H
  11. #define A_H
  12. AA
  13. #endif

  14. C

  15. X
复制代码
最终交给编译器看到的代码就是:
  1. AA
  2. B
  3. C
  4. X
复制代码
只要A_H是唯一的, AA就不会重复出现。

就解决了这个问题, 一般情况就是这么用的, 是为惯例

4. 外部头文件保护符

上面的用法是"内部头文件保护符"。 a.h的保护符是使用在a.h里。
另外一种用法是"外部头文件保护符", 如:

------ a.h ------
AA

------ b.h ------
#ifndef A_H
#define A_H
#include "a.h"
#endif
B

------ c.h ------
#ifndef A_H
#define A_H
#include "a.h"
#endif
C

当X同时包含b.h和c.h时, 最终效果和内部头文件保护符差不多。

两者对比, 外部的优势是可以减少打开a.h的次数。
而内部保护符可以降低a.h和b.h,c.h之间的耦合。


5. 定义保护符

马上就要到主题了……

将头文件保护符的用法扩展一下, 就变成了定义保护符(这个名字是我捏造的)。
保护的不是某个"头文件" 而是某个"定义", 如:

------ a.h ------

#ifndef A_X
#define A_X
struct x { ... };
#endif

#ifndef A_M
#define A_M
#define M ...
#endif

...


b.h和c.h直接包含a.h, 最终效果也是一样。


6. 重复的定义保护符

到主题了……
同样是一个捏造的词。

假设:
b.h包含a.h是为了获得struct x的定义。
而c.h包含a.h是为了获得宏M的定义。

除了上面作法, 还有另一种做法:

a.h和上面差不多
  1. #ifndef X
  2. #define X
  3. struct x { ... };
  4. #endif

  5. #ifndef M
  6. #define M ...
  7. #endif
复制代码
而b.h和c.h并不包含a.h, 而是直接将需要的定义写在b.h和c.h中
------ b.h ------
  1. #ifndef X
  2. #define X
  3. struct x { ... };
  4. #endif

  5. B
复制代码
------ c.h ------
  1. #ifndef M
  2. #define M ...
  3. #endif

  4. C
复制代码
这样做其实耦合比外部头文件保护符还要高, 所以一般是不会采用的。

C的标准头文件必须这样做
因为C89有一个要求, 具体我不记得了。
要么是要求标准头文件不能包含其他标准头文件。
要么是要求标准头文件不能包含任何其他文件。
(C++和C99取消了这个要求)


stdio.h是C89的标准头文件。
例如, 它需要定义一个size_t, 作为一些函数的参数类型。
而另外有一些标准头文件也会有size_t。
所以这些头文件中的size_t都是这样提供的:
  1. #ifndef _SIZE_T_DEFINED
  2. #define _SIZE_T_DEFINED 
  3. typedef unsigned xxx size_t;
  4. #endif
复制代码
或者也可能将若干定义分组, 共用一个保护符。



不知道lz遇到的是不是这个情况?