如何检查表达式在 C 中是否为常量?

How can I check that an expression is constant in C?

假设我有一个场景,我需要确保我的代码中使用的值是编译时常量(例如,可能是对 P10 rule 2 "fixed loop bounds" 的严格解释)。我如何在 C 语言级别强制执行此操作?

C 在语言级别支持整数常量表达式 的概念。一定有可能想出一种方法来利用这一点,以便只有符合此规范的值才能在表达式中使用,对吗?例如:

for (int i = 0; i < assert_constant(10); ++i) {...

一些部分解决方案不够通用,无法在多种情况下使用:

当然有一些方法可以在可重复的宏中隐藏常量检查,传递值(因此它可以用作表达式),并且不需要语句行或引入额外的标识符?

原来有办法!

虽然本地分配的数组在 C 中允许具有可变长度,但标准明确要求此类数组 not 具有显式初始化程序。我们可以通过给数组一个初始化列表来强制禁用 VLA 语言特性,这将强制数组大小为整数常量表达式(编译时常量):

int arr[(expression)] = { 0 };

初始化器的内容无关紧要; { 0 } 将始终有效。

这个方案还是比enum方案略逊一筹,因为它需要一个语句,引入一个名字。但是,与枚举不同,数组可以匿名(作为复合文字):

(int[expression]){ 0 }

由于复合文字有一个初始值设定项作为语法的一部分,因此它不可能成为 VLA,因此仍然保证需要 expression 作为编译时常量。

最后,因为匿名数组是表达式,我们可以将它们传递给 sizeof,这为我们提供了一种传递 expression:

原始值的方法
sizeof((char[expression]){ 0 })

这有一个额外的好处,那就是保证数组在运行时永远不会被分配。

最后,再多一点总结,我们甚至可以处理零值或负值:

sizeof((char[(expression)*0+1]){ 0 }) * 0 + (expression)

这在设置数组大小(永远为1)时忽略了expression的实际,但仍考虑其恒定状态;然后它还会忽略数组的大小, returns 仅忽略原始表达式,因此对数组大小的限制 - 必须大于零 - 不需要应用于返回值。 expression 是重复的,但这就是宏的用途(如果编译通过,它不会被重新计算,因为 a. 它是一个常量,并且 b. 第一次使用是在 sizeof 中)。因此:

#define assert_constant(X) (sizeof((char[(X)*0+1]){ 0 }) * 0 + (X))

为了加分,我们可以使用非常相似的技术来实现 static_switch 表达式 ,方法是将数组大小与 C11 的 _Generic 相结合(这可能没有太多实际用途,但可能会取代一些不流行的嵌套三元组的情况):

#define static_switch(VAL, ...) _Generic(&(char[(VAL) + 1]){0}, __VA_ARGS__)
#define static_case(N) char(*)[(N) + 1]

char * x = static_switch(3,
             static_case(0): "zero",
             static_case(1): "one",
             static_case(2): "two",
             default: "lots");
printf("result: '%s'\n", x); //result: 'lots'

(我们使用数组的地址来生成一个明确的数组指针类型,而不是让实现决定 _Generic 是否将数组提升为指针;截至 2016 年 4 月,这种歧义是DR 481 及其随后的 TC 在语言中修复。)

这比 assert_constant 稍微严格一些,因为它不允许负值。但是,通过在控制表达式 所有大小写值中都放置一个 +1,我们至少可以让它接受零。