为什么零长度VLA UB?

时间:2015-10-26 17:59:23

标签: c arrays variable-length-array

2011年标准明确规定......

  

6.7.6.2数组声明符

     
      
  1. 如果size是一个不是整数常量表达式的表达式:如果它出现在a中   在函数原型范围内的声明,它被视为被替换为   *;除此以外,   每次评估时,它的值应大于零。每个实例的大小   可变长度数组类型在其生命周期内不会改变。如果size表达式是sizeof运算符的操作数的一部分,并且更改size表达式的值不会影响运算符的结果,则无论是否指定   评估大小表达式。
  2.   

它的设计,但以下代码似乎是合理的。

size_t vla(const size_t x) {

  size_t a[x];
  size_t y = 0;

  for (size_t i = 0; i < x; i++)
    a[x] = i;

  for (size_t i = 0; i < x; i++)
    y += a[i % 2];

  return y;
}

Clang似乎为它生成合理的x64程序集(没有优化)。显然索引零长度VLA并没有意义,但访问超出边界会调用未定义的行为。

为什么零长度数组未定义?

3 个答案:

答案 0 :(得分:6)

int i = 0;
int a[i], b[i];

a == b?它不应该是 - 它们是不同的对象 - 但避免它是有问题的。如果您无条件地在ab之间留出空隙,则会在i > 0案例中浪费空间。如果您检查i == 0是否只留下差距,那么您就会在i > 0案例中浪费时间。

多维数组会变得更糟:

int i = 0;
int a[2][i];

你可以在两个变量之间填充,但你可以在哪里填充?如果不打破sizeof (int[2][i]) == 2 * i * sizeof (int)的不变量,就无法做到这一点。如果您没有填充,则a[0]a[1]具有相同的地址,并且您正在打破其他重要的不变量。

这是一个不值得定义的头痛。

答案 1 :(得分:4)

虽然我们可以看到gcc supports zero length arrays an extension,但显然它们很有用。从标准的角度来看,它似乎会产生一些问题,因为现在每个对象都应该有一个唯一的地址。我们可以从C99和C11标准草案6.5.9中的平等运算符中看出这一点:

  

两个指针比较相等,当且仅当两个都是空指针时,两个指针都指向   相同的对象(包括指向对象和开头的子对象的指针)或函数,   两者都是指向同一个数组对象的最后一个元素之后的指针,或者一个是指针   到一个数组对象的末尾,另一个是指向另一个数组对象的开头的指针   紧接着地址中第一个数组对象的数组对象   space.94)

所以这需要一些特殊的外壳,并且可以使用替代方法提供大多数有用的功能,例如flexibile数组。

它也可能需要在其他地方进行更改,如M.M.在6.3.2.1 Lvalues,数组和函数指示符中指出数组到指针衰减:

  

[...]表达有   类型''数组''转换为类型为''指向类型'指针的表达式   到数组对象的初始元素,而不是左值[...]

这似乎需要进行一些非平凡的改变才能获得最小的额外收益。

答案 2 :(得分:1)

看C标准:

C11-6.7.6.2数组声明符(p1):

  

[...]如果表达式是常量表达式, 的值应大于零。 [...]

(P5):

  

如果size是一个不是整数常量表达式的表达式:如果它出现在函数原型范围的声明中,则将其视为由*替换;否则,每次评估时, 的值应大于零。 [...]

4。一致性:

  

如果&#34;应该&#34;或&#34;不得&#34;违反约束或运行时约束之外的要求,行为未定义。未定义的行为在本国际标准中以“未定义的行为”字样表示。或遗漏任何明确的行为定义。 这三者的重点没有区别;他们都描述了未定义的行为&#34;

因此,声明零大小的数组会导致程序的未定义行为。