【问题标题】:Why Are Zero Length VLAs UB?为什么零长度 VLA 是 UB?
【发布时间】:2016-01-25 22:51:15
【问题描述】:

2011 年标准明确规定...

6.7.6.2 数组声明符

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

这是人为的,但下面的代码似乎是合理的。

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 没有意义,但是越界访问会调用未定义的行为。

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

【问题讨论】:

  • C 也不允许零长度的非 VLA;不允许它们作为 VLA 是一致的。 GCC(因此也有 clang)具有允许零长度数组的扩展。你可以争论这是否好。
  • “显然索引零长度 VLA 没有意义,但越界访问会调用未定义的行为。” - 示例中都没有发生。
  • @KarolyHorvath 我的想法是索引零长度的东西已经被禁止了。与空列表或零长度向量类似,只要值没有被索引(语言已经禁止),零长度数组对我来说是有意义的。
  • @JonathanLeffler 有趣的是,std::array in C++ does special case 的长度为零。
  • @Jason,该语言不禁止索引零长度数组-语法允许!只有越界访问的结果是UB。这适用于所有数组,与类型或大小无关。

标签: c arrays variable-length-array


【解决方案1】:
int i = 0;
int a[i], b[i];

a == b?它不应该——它们是不同的对象——但避免它是有问题的。如果您无条件地在ab 之间留下一个空白,那么您在i &gt; 0 案例中浪费了空间。如果您检查是否i == 0 并且只留下一个空白,那么您在i &gt; 0 案例中浪费时间。

多维数组会变得更糟:

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

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

这是一个不值得定义的令人头疼的问题。

【讨论】:

  • "如果是肯定的,我会为 a 和 b 分配相同的地址" - 这是脑放屁还是一些错字?这没有任何意义......
  • @KarolyHorvath: "for positive i" 附加到它前面的短语,而不是后面的短语。生成的代码(对于积极的i 来说是合理且节省空间的)将为ab 分配相同的地址为i == 0
  • 刮掉整个文本并从零开始。它仍然是一个巨大的混乱。
  • @KarolyHorvath:对我来说似乎并不难解析,但我写了它。现在怎么样?
  • 我喜欢我们的回答,但是“如果你在 a 和 b 之间无条件地留出一个间隙,那么你在 i > 0 的情况下是在浪费空间”,如果“无条件”意味着“至少一个元素”(如果你明白我的意思的话)。如果 i>0 则没有浪费。
【解决方案2】:

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

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

因此,这需要一些特殊的外壳,并且可以使用其他方法提供大部分有用的功能,例如灵活数组。

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

[...]一个表达式 type ‘‘array of type’’ 被转换为类型为 ‘‘pointer to type’’ 的表达式,它指向 到数组对象的初始元素并且不是左值[...]

这似乎需要进行一些重要的更改才能获得最小的附加收益。

【讨论】:

  • "或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在第一个数组对象之后地址空间”意味着必须分配至少一个元素,但是,x 仍然可以是零,功能和实际,运行时现在将分配一个元素。如果我们期望 a 和 b 在内存中是连续的,并且想要计算 b 之后的 c 的地址为 c==a+sizeof(a)+sizeof(b),那么 UB 将跟随,因为 a 和 b 现在不是零大小(除非 sizeof 可以处理)。
  • 它和malloc(0)有什么不同?
  • @Jason from 7.20.3 如果请求的空间大小为零,则行为由实现定义:返回空指针,或者行为好像大小是一些非零值,但返回的指针不得用于访问对象
  • 谢谢。实际上,定义的实现对我来说似乎更直观。不过,我不是编译器作者或语言维护者。
【解决方案3】:

看C标准:

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

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

(p5):

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

4。一致性:

如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义。未定义的行为在本国际标准中以“未定义的行为”一词或省略任何明确的行为定义来表示。 这三者在侧重点上没有区别;它们都描述了“未定义的行为”

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

【讨论】:

  • "因此,C 不允许数组长度为零。" - 为什么?那里的解释有很大的跳跃。
  • @KarolyHorvath;我不是说通俗地说吗?现在我正在等待你解释清楚的答案。
  • 我只是指出我不明白你的解释。我希望你习惯于无限繁忙循环或无限阻塞 API 调用,因为我没有解释。并不是说我需要对您的模糊解释发表评论。
  • 我们是否可以使用pointerToObject = &amp;vla[0] 之类的方式访问vla[0]
  • @Jongware;不,我们不能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-04
  • 1970-01-01
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多