同时执行#ifdef 和#ifndef 的 C++ 预处理器

C++ preprocessor executing both #ifdef and #ifndef

所以我目前正在研究使用 OpenCL 的东西。 OpenCL 规范为用户提供了一个指令,该指令必须在包含 header (cl.h)

之前包含

#define CL_TARGET_OPENCL_VERSION 110

这基本上定义了他们想要使用的版本。假设我正在创建一个库并且我希望我的用户定义它而不是我在我的文件中定义它。我做的是。

-----main.cpp---
#define CL_TARGET_OPENCL_VERSION 110
#include "library.h"
-------x---------

----library.h-----
#ifdef CL_TARGET_OPENCL_VERSION
#pragma message("def")
#endif

#ifndef CL_TARGET_OPENCL_VERSION
#pragma message("ndef")
#endif
.... include other headers.
--------x---------

并且编译器同时打印 defndef 消息。并且 OpenCL 库还会发出未定义的警告。我认为库 header 会被替换到 main 中,它只会打印 def 消息。有什么我理解错了吗?

我对预处理器从哪里开始感到特别困惑?如果是从main.cpp开始,从上到下,那么肯定定义了宏。之后它看到库包含,然后它应该只打印 def 消息但它会打印两者。

这让我相信预处理器在将 header 文件包含在 main 之前确实扫描了它?不知道为什么。此外,我已确保库 header 未包含在其他地方。

我注意到的一件有趣的事情是,如果我这样做

-----helper.h---
#define CL_TARGET_OPENCL_VERSION 110    
-------x---------

----library.h-----
#include helper.h    
#ifdef CL_TARGET_OPENCL_VERSION
#pragma message("def")
#endif

#ifndef CL_TARGET_OPENCL_VERSION
#pragma message("ndef")
#endif
.... include other headers.
--------x---------

它打印 def 消息 "twice"。如果有人能解释这一切,我将不胜感激。

编辑:- 我正在编译的文件是 main.cpp library.hlibrary.cpp Library.cpp 像往常一样从头开始包含 library.h。也许是其他 cpp 导致了问题?

优先使用#ifndef XXXX_h #define XXXX_h #endif 而不是#pragma once

如果您的 #include 搜索路径足够复杂,编译器可能无法区分具有相同基名 (e.g. a/foo.h and b/foo.h) 的两个 header 之间的区别,因此 #pragma once 其中之一会抑制两者。它也可能无法分辨两个不同的相对包含 (e.g. #include "foo.h" and #include "../a/foo.h" 引用同一个文件,因此 #pragma once 将无法在应该包含的时候抑制冗余包含。

这也会影响编译器避免使用 #ifndef 守卫重新读取文件的能力,但这只是一种优化。使用 #ifndef 守卫,编译器可以安全地读取它不确定已经看到的任何文件;如果它错了,它只需要做一些额外的工作。只要没有两个 header 定义相同的守卫宏,代码就会按预期编译。而如果两个header确实定义了同一个守卫宏,程序员可以进去改其中一个

#pragma 曾经没有这样的安全网——如果编译器对 header 文件的标识有误,无论哪种方式,程序都将无法编译。如果遇到此错误,您唯一的选择是停止使用 #pragma 一次,或重命名其中一个 header。 header 的名字是你的 API 合同的一部分,所以重命名可能不是一个选项。

(为什么使用 #pragma 有问题的简短版本是 Unix 和 Windows 文件系统 API 都没有提供任何机制来保证告诉你两个绝对路径名引用同一个文件。)

在 C/C++ 程序中,编译器分别处理每个 .c 和 .cpp 文件。

编译器相互独立地构建每个源文件(不是头文件,只有 .c 和 .cpp 文件)(此源文件称为 compilation unit)。

因此,当构建 main.cpp 时,编译器会找到您添加到 main.cpp 文件顶部的 #define CL_TARGET_OPENCL_VERSION 110,并发出 def 消息。

但是当编译器构建 library.cpp 文件时,它没有找到定义的版本,所以它发出 ndef 消息。

所以,按照这个解释,在你的最后一个例子中,当你将定义添加到 .h 文件时,编译器发出两次 def 消息是完全正常的,一次是 main.cpp 文件和一次 library.cpp 文件。


现在的问题是您应该在哪里添加定义,以便使程序构建一致,所有 .cpp 文件的版本相同。

通常,所有 IDEs 都有一些配置页面,您可以在其中为所有项目添加全局定义,这些定义首先 "inserted" 进入所有编译单元。因此,当 IDE 调用编译器时,它会将相同的定义传递给所有编译单元。您应该在此页面中添加此类定义。

在您的 IDE(我使用的是 Code::Blocks,v 17.12)中,您可以在菜单中找到此页面:Project / Build Options

对于每种类型(调试或发布),您必须转到选项卡 Compiler Settings,然后转到子选项卡 #defines。在那里你可以添加全局定义,如果你在 Debug 或 Release 模式下构建,它可能会有所不同(当然,如果你在两种模式下设置相同,它们将是相同的)。

一旦您在此处添加了您的定义,请将其从 main.cpplibrary.h 以及您可能已添加它的任何其他地方删除,以避免重复。


来自关于便携性的评论:

您有多种选择:

  • 始终使用 Code::Blocks:这将是最简单的方法,因为您可以将 Code::Blocks 项目与源文件一起传递,一切都已经设置好了。

  • 使用 cmake,这是一个脚本构建系统,您可以在其中设置定义等,方法与使用 IDE 相同。 cmake 比 Code::Blocks 使用更广泛,所以也许它是一个更好的选择。

  • 添加一个新的options.h头文件,在其中设置所有defines,并将其包含到所有.c/.cpp中。此设置还有一个额外的好处,即对于不同的系统,仅更改 options.h 文件构建可能完全不同。这是 IDE 正在执行的手动设置。它的优点是不依赖于外部工具,缺点是你必须记得在所有添加到项目中的新.cpp文件中添加它。

我的建议是cmake,正如其他人所说。