从编译器获取参数时,有没有办法避免预处理器宏?

Is there a way to avoid preprocessor macros when taking an argument from the compiler?

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

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

#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 实现)

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

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

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

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

#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++ 预处理器语言好)。

另一种方法是使用 link 时间常数。 linker 符号通常是函数或全局变量的名称。具有静态存储持续时间的东西。符号的值是对象的地址。但是可以在给 linker 的命令中定义您想要的任何符号。这是一个示例 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 的。

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