HAVE_*宏的目的是什么?

4

我正在将一个Autotools项目中的一些C/C++源文件重用于一个CMake项目中,我看到许多源文件上都有类似以下的代码行:

#ifdef HAVE_UNISTD_H
#include <unistd.h>  // for getpid()
#endif

如果 getpid() 是可选的,且其调用被等效的 HAVE_UNISTD_H 指令包围,那么我将理解这个结构的目的。然而,没有 HAVE_UNISTD_H ,源文件就无法编译,报告 getpid() 未定义的错误。这比编译器让我知道找不到 unistd.h 要复杂得多。
当然,这只是一个例子。其他常见的宏包括 HAVE_STDINT_H, HAVE_INTTYPES_H 等,它们的存在是编译源文件所必需的。
为什么要包括 HAVE_* 开关?我认为它们只会带来不利影响:
  • 重用这样的源文件需要确保正确的头文件存在正确的 HAVE_* 宏已定义。
  • 如果出现错误,开发者会收到更加难懂的消息,即编译器不会报告根本原因(找不到头文件),而是一个次要错误(类型/函数未找到)。
  • 源文件会变得有些冗长,阅读起来有些繁琐,即 #include#ifdef 混杂在一起。

1
这是C还是C++?对于大多数当前的编译器来说,C的默认模式不会发出关于未声明getpid()的错误消息。这既是支持宏的原因,也是反对宏的原因,具体取决于你问谁。 - user743382
请参见 https://dev59.com/EHI-5IYBdhLWcg3w0cOG。 - Tom Zych
请注意,从版本5开始,GCC默认为C11模式,并且对隐式声明的函数至少会生成警告消息。只有旧版本的GCC默认为C90。 - Jonathan Leffler
@JonathanLeffler 我非常清楚这一点,这并不与我的评论相矛盾。在GCC 5、6和7中仍然只是一个警告。 - user743382
请注意,只对 #ifdef HAVE_UNISTD_H / #include <unistd.h> / #endif 进行代码编写而没有任何后备方案,这与 HAVE_UNISTD_H 检查的意图相违背。 代码应为缺失内容提供一个通用声明 ——例如 int getpid(void);。 否则,检查就是毫无意义的。 这是最初的想法。 在 C90 年代,你可以通过隐式函数声明逃脱 ——但如果 Microsoft 实际上实现了 C99 或 C11 (二者皆有也好),则 C90 应该早已被遗忘。 - Jonathan Leffler
这是因为地球上没有人真正理解autotools,所以configure脚本是通过从类似项目中复制粘贴片段来创建的。 没有超出“它似乎可以工作”的原因。 至少在我的经验中是这样。 - el.pescado - нет войне
2个回答

12
大多数HAVE_xxx_h守卫是POSIX出现并标准化头文件之前的遗留物。在90年代初,你可能会轻易地遇到一个有getpid()但没有工作的unistd.h的系统——该函数将简单地在另一个头文件中声明,或者根本不会被声明,但由于K&R和C89 C中声明是可选的,因此它仍然可以工作(只要其返回值是int大小)。

当时使用的系统之间甚至存在更奇怪的问题。例如,有些系统提供了time.h,有些则提供了sys/time.h,还有一些同时提供了两者——但在最后一类系统中,尝试实际包含这两个文件会导致编译错误!支持这样广泛的系统,尽可能不提前列出所有这些系统,是Autoconf的明确设计目标之一,其中一些长期不相关的技巧仍然被仔细记录

除了以上的问题,将头文件名称与函数支持分离在将代码移植到非POSIX系统(如Windows)时会很有用。在这些系统上,posix头文件可能会丢失或损坏,并且实际的函数定义来自于可移植性库,例如gnulib。

由于在ANSI C之前声明是可选的,包括C89/C90。 - user743382
1
在C99和C11中,严格来说这是一种语法错误(请参见主要表达式,脚注明确说明了这一点),但编译器允许接受语法无效的代码作为扩展,并且GCC出于向后兼容性而这样做(我不认为GCC是唯一这样做的)。 - user743382
@hvd 当您提到语法错误时,您是否指的是ANSI之前的声明?在回答的上下文中,我指的是使用一个函数[没有被完全声明](http://www.tutorialspoint.com/compile_c_online.php?PID=0Bw_CjBb95KQMZVhXMF9yVHpSbGc)(但在单独的编译单元中定义)。这样的函数调用不能是语法错误,因为调用的语法是正确的,但仍然会被例如C++拒绝。 - user4815162342
1
不,那真的被归类为语法错误了,我已经提供了参考资料。函数调用语法要求调用一个表达式,而未声明的标识符不被视为表达式,即使它们看起来像是。 - user743382
引用TutorialsPoint并不能加强任何人的观点。例如,他们有一个递归示例,其中“15的阶乘为2004310016”。当然,正确答案是1307674368000,但这不适合32位值,而2004310016是1307674368000%(2 ^ 31)。因此,他们没有注意到有符号整数算术溢出-也称为未定义行为。不好! - Jonathan Leffler
显示剩余2条评论

1
为什么要包含HAVE_*守卫?我觉得它们只会带来不利影响:...
如果源代码使用了另一种实现方式,那么你肯定不希望因为缺少包含文件而出错。
愚蠢的例子:1
#ifdef HAVE_UNISTD_H
#include <unistd.h>  // for getpid()
#endif
#ifdef HAVE_WINDoWS_H
#include <windows.h> // for GetProcessId()
#endif

我知道Windows支持getpid()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接