同时执行#ifdef和#ifndef

时间:2018-07-24 06:09:48

标签: c++ c-preprocessor

所以我目前正在研究使用OpenCL的东西。 OpenCL规范为用户提供了一个指令,必须在包含标头(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库还会引发警告,提示它未定义。我以为库头将被替换为main,并且只会打印def消息。我有什么不对的地方吗?

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

这使我相信预处理器会在将头文件包含在主文件中之前对其进行扫描吗?不知道为什么。另外,我保证库头文件不包含在其他地方。

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

-----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消息“两次”。如果有人能解释所有这些,我将不胜感激。

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

2 个答案:

答案 0 :(得分:4)

在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文件的版本都相同。

通常,所有IDE都有一些配置页,您可以在其中为所有项目添加全局定义,这些全局定义先“插入”到所有编译单元中,然后再进行其他操作。因此,当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一起去。

答案 1 :(得分:1)

比#pragma优先使用#ifndef XXXX_h #define XXXX_h #endif

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

这也会影响编译器避免使用#ifndef防护措施重新读取文件的能力,但这只是一种优化。使用#ifndef防护,编译器可以安全地读取不确定的文件。如果错了,它只需要做一些额外的工作。只要没有两个头定义相同的保护宏,该代码就会按预期进行编译。而且,如果两个标头确实定义了相同的保护宏,则程序员可以进入并更改其中一个。

#pragma曾经没有这样的安全网-如果编译器在头文件的身份方面有误,无论哪种方式,程序都将无法编译。如果遇到此错误,则唯一的选择是停止使用#pragma一次,或重命名其中一个标头。标头名称是您的API合同的一部分,因此重命名可能不是一种选择。

(为什么使用#pragma会有问题,因为Unix和Windows文件系统API均未提供任何机制来保证告诉您两个绝对路径名是否引用同一文件)。 >

相关问题