2015-11-22 118 views
5

假设我有一个场景,我需要确保在我的代码中使用的值是编译时常量(例如,可能是对P10 rule 2“固定循环边界”的严格解释)。我如何在C语言级别执行此操作?如何检查表达式在C中是否为常量?

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

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

一些局部的解决方案,是不是真的足够一般在多种情况是有用的:

  • 位地址:在C之前C11实现static_assert一个经典的策略是use a bitfield whose value would be illegal when a condition failed

    struct { int _:(expression); } 
    

    虽然这可以很容易地缠绕用作表达式的一部分,它根本不是一般的 - 最大值为expression“[可以]不超过将被指定的类型的对象的宽度,其中冒号和表达式被省略”(C11 6.7.2.1),其放置非常低关于expression的幅度的便携式限制(一般可能是64)。它也可能不是负面的。

  • 枚举:一个enum需求的任何初始化表达式是整数常量表达式。但是,enum声明不能嵌入到表达式中(不像struct定义),因此需要声明它自己的声明。由于枚举器列表中的标识符被添加到周围的范围,所以我们每次都需要一个新名称。 __COUNTER__不是标准化的,所以没有办法从宏中实现这一点。

  • case:再次,参数表达式为case行必须是整数常量。但是这需要围绕switch声明。这并不比enum好很多,这是你不想隐藏在一个宏内部的东西(因为它会生成真实的语句,即使优化器很容易删除)。

  • 数组声明:自C99以来,数组大小甚至不一定是常数,这意味着它无论如何不会产生所需的错误。这也是一个声明,需要在周围的范围内引入一个名字,并且遭受与enum相同的问题。

肯定有一些方法来隐藏在宏这是重复的,经过(因此它可以作为一个表达式)的值,并且不需要一个语句行或引入额外的标识符持续检查?

+1

另请参阅[此SO帖子](http://stackoverflow.com/questions/9274532/how-to-check-if-a-parameter-is-an-integral-constant-expression-in-ac-preprocess ?rq = 1)? – quantdev

+0

@quantdev *叹*真的以为我已经为这个话题进行了详尽的搜索...... – Leushenko

回答

5

原来有办法!

虽然本地分配的数组允许在C中具有可变长度,但标准明确要求此类数组而不是具有明确的初始值设定项。我们可以通过给数组的初始化列表,这将迫使数组大小是整数常量表达式强制禁用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 ,但仍然认为其不变的地位;那么它也会忽略数组的大小并仅返回原始表达式,所以对数组大小的限制 - 必须大于零 - 不需要应用于返回的值。 expression是重复的,但这就是宏的用途(如果编译的话,它不会被重新计算,因为它是一个常数,b。第一个用法在sizeof之内)。因此:

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

奖励积分,我们可以使用一个非常类似的技术来实现static_switch表达,通过数组大小与C11的_Generic组合(这可能没有多少实际用途,但可能代替嵌套ternaries的某些情况下,这是不是流行):

#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是否将数组提升为指针)

这比assert_constant的限制性稍强,因为它不会承认负值。通过在控制表达式中加上+1,尽管我们至少可以让它接受零。

+0

非常令人印象深刻的工作! – ApproachingDarknessFish

相关问题