【问题标题】:Is there a way to avoid preprocessor macros when taking an argument from the compiler?从编译器获取参数时,有没有办法避免预处理器宏?
【发布时间】:2020-05-29 17:26:39
【问题描述】:

我正在编写一个库,它需要进行一些编译时间计算,并构建一个编译时间常量数组。问题是我需要一种方法来指定这个数组的最大大小......我知道的唯一方法是让它成为传递给编译器的可配置选项。

然后您可以将它与预处理器指令一起使用,例如:

#ifndef MAX_SIZE
    constexpr auto maxSize = 42; // Some default value if no MAX_SIZE is specified
#else
    constexpr auto maxSize = MAX_SIZE;
#endif

要设置使用 gcc 编译时的最大大小,可以使用选项-DMAX_SIZE=<desired_size> 编译代码。

我遇到的问题是它涉及使用预处理器宏从编译器获取MAX_SIZE 参数。预处理器宏被认为是邪恶的原因有很多(我不会在这里讨论,因为这不是问题的重点)。

有什么方法可以在不使用预处理器宏的情况下实现此功能? (我有多达 C++20 可用,因此请随意使用您的解决方案 -- 大部分情况下,其中一些尚未由 gcc 10 实现

【问题讨论】:

  • 预处理器宏被认为是邪恶的它们只有在不用于它们预期的工作时才是邪恶的,例如goto。这对他们来说是一个完美的用例。
  • 你可以有一个头文件config.h或类似的,你可以在其中设置例如直接maxSize
  • 通常不会给出例外,因为它的“新手”正在询问并且他们不需要了解“高级”例外。说永远不要使用它更容易,然后一旦他们真的有一个案例,让他们询问是否可以。
  • @AndréCaceres MAX_SIZE 可以定义也可以不定义。
  • @AndréCaceres 问题不在于定义 maxSize,而在于如何将 MAX_SIZE 注入代码

标签: c++ gcc macros preprocessor compile-time-constant


【解决方案1】:

还有其他方法,但您可能不想使用它们。您正在做的是很好地使用预处理器。虽然我会写这样的东西:

#ifndef MAX_SIZE
#define MAX_SIZE 42
#endif
constexpr size_t maxSize = MAX_SIZE;

这样实际代码部分、变量类型、名称等只需要编写一次。还要考虑:

#indef MAX_SIZE
#error "You need to define MAX_SIZE to compile this code, e.g. -DMAX_SIZE=42"
#endif

这样,当他们不想使用默认值时,人们不会使用默认值,因为他们不知道如何定义它,或者构建系统中的某些东西使 -D 标志在某个地方丢失了。

但还有其他方法可以避免使用预处理器宏!

自己生成源代码。虽然这可能看起来很复杂,而且通常很复杂,但有一些方法可以让它变得不那么复杂。构造代码,使必须生成的部分很小。例如,从maxSize 的单个定义中派生其他值,而不是生成所有需要知道大小的代码。在某些情况下,也有一些系统已经可以做到这一点。例如,如果使用 CMake,请创建一个 header.h.in 文件,如下所示:

constexpr size_t maxSize = @MAXSIZE@;

然后把它放到一个 CMakeLists.txt 文件中:

set(MAXSIZE 42)
configure_file(header.h.in header.h @ONLY ESCAPE_QUOTES)

项目建好后,cmake会将header.h.in转成header.h,@MAXSIZE@改成42。 这没有使用 C++ 预处理器,但我们有效地使用 CMake 预处理器在编译文件之前对其进行预处理,所以真的,有什么不同吗?它只是一种不同的预处理器语言(它不如 C/C++ 预处理器语言)。

另一种方法是使用链接时间常数。链接器符号通常是函数或全局变量的名称。具有静态存储持续时间的东西。符号的值是对象的地址。但是可以在链接器的命令中定义您想要的任何符号。这是一个示例 C 文件:

#include <stdio.h>
char array1[1];
extern array2[];
int main(void) { printf("%p %p\n", array1, array2); return 0; }

使用 gcc 编译为gcc example.c -Wl,--defsym=array2=0xf00d

就像它打印array1的地址一样,它会打印0xf00d作为array2的地址。因此,我们在代码中注入了一个常量,而不使用任何预处理器,无论是 C 还是 CMake。

但是编译器不知道这个值,只有链接器知道。它不是“整数常量表达式”,不能在某些地方使用,例如大小写标签或具有静态存储持续时间的对象的大小。因为编译器需要知道那些编译代码的确切值。编译器在不知道array1array2 的确切值的情况下为 printf 调用生成了代码。对于 case 语句标签,它无法做到这一点。这实际上是因为“整数常量表达式”存在于 C/C++ 标准中,并且与常量和具有整数类型的表达式不同。

【讨论】:

  • 我完全忘记了 CMake 可以做这样的事情!虽然 CMake 预处理器不是那么强大,但我认为这可能是我想要定义它的地方,因为这样语法将不依赖于编译器。同样使用 CMake 意味着我在 CMake 之前不需要额外的配置步骤!
【解决方案2】:

据我所知,除了 GCC 中的宏定义之外,没有其他基于编译器的机制来参数化编译。

在编译器之外,您可以使用元编程:编写一个在编译之前生成源代码的程序。这与使用预处理器基本相同,只是您可以选择任何语言或工具来代替标准预处理器。

这种选择的缺点是由于额外的步骤而增加了构建的复杂性。这种方法可用于回避预处理器的问题,但可能会引入自定义处理器的新问题。

【讨论】:

  • 啊,我想这解释了为什么像gcc 这样的工具在编译之前有一个“配置”步骤,如果你从源代码构建它们。我觉得添加一个“配置”步骤对我的应用程序来说可能是多余的,因为我只有一个参数要传递。
猜你喜欢
  • 2016-05-08
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
相关资源
最近更新 更多