C99 式 VLA 都有哪些技术缺点? [关闭]
Posted
技术标签:
【中文标题】C99 式 VLA 都有哪些技术缺点? [关闭]【英文标题】:What technical disadvantages do C99-style VLAs have? [closed]C99 式 VLA 有哪些技术缺点? [关闭] 【发布时间】:2012-09-06 15:05:53 【问题描述】:我从很多人那里听说,C99 中引入的可变长度数组很糟糕。 IRC 上的一些人一分钟前说“我不认为 C++ 会获得 VLA,strosoup 对它们提出了一些非常负面的评价”。
那些人讨厌 VLA 的原因是什么?
【问题讨论】:
我听说这可能被视为一个巨魔问题。请不要以为是,我只是在寻找更喜欢 std::vector 之类的理由。 C11 甚至没有弃用 VLA 之类的吗? @Xeo 不推荐使用,它们已成为可选的“可变长度数组是实现不需要支持的条件特性;参见 6.10.8.3。”。 @ChristianRau,C 标准中有几个部分是可选的,可以使用宏进行测试。我觉得没有什么令人震惊的。"Making them mandatory would effectively prohibit C implementations on hardware with 9-bit bytes"
这样的废话总结了为什么 ISO 标准糟糕,不允许它们偏爱某种技术。想象一下,如果严格指定字节大小和 int 类型大小,如果有符号整数始终是二进制补码,如果浮点数对每个浮点数表示都有指定的类型,如果 Unicode 是唯一允许的符号表等等,那么 C 会有多好。 ISO作为防火墙保护我们免受理智的技术改进的影响,他们做得非常好。
【参考方案1】:
尽管可变长度数组存在问题,但应牢记它们是如何产生的:作为 alloca()
的替代品,可以说是 even more problematic。
虽然在 PDP-11 上实现起来很简单,但在其他架构和 Ritchie 和 Thompson removed it from their implementation 上却不是这样。
然而,可变大小的自动分配显然足够有用,以至于alloca()
尽管存在问题,但它还是复活了(特别是,它不能用于任何可能调用任意函数的地方,并且在许多架构上它必须是编译器无论如何内置)。 C 工作组同意提供这样的功能,但认为可变长度数组是更好的解决方案。
如果您查看 C99 添加的功能(复数、类型通用数学、restrict
、...),您应该注意到其中很多功能旨在使 C 成为更好的数值计算语言。可变长度数组在那里也很有用,我相信当时 Fortran 已经有了它们。此外,它们的引入还导致了可变修改的派生类型(例如,指向可变大小数组的指针),这在处理矩阵时特别有用。
【讨论】:
如果有一个函数stalloc()
类似于 alloca() 但需要在 LIFO 中调用 stfree()
以便在函数通过任何方式退出之前清理分配,那将是可以实现的在任何平台上。支持通过 longjmp() 放弃块可能有问题,因此应该是可选的,但 LIFO 分配器的语义将比与 VLA 相关联的更通用。【参考方案2】:
正如其他人所指出的,VLA 使堆栈帧溢出变得非常容易。我不是编译器编写者,但我的理解是 VLA 也可能成为支持的错误(它们现在在 C2011 中是可选的)。并且它们的使用仅限于块或函数范围;您不能在文件范围内使用 VLA,它们也不能有外部链接。
不过,我不希望看到 VLA 语法消失;当动态分配内部维度直到运行时才知道的多维数组时,它非常方便,例如:
size_t r, c;
// get values for r and c
int (*arr)[c] = malloc(r * sizeof *arr);
if (arr)
...
arr[i][j] = ...;
...
free(arr);
一个连续分配(和一个对应的free
),和我可以将它下标为二维数组。替代方案通常意味着零碎分配:
size_t r, c;
...
int **arr = malloc(sizeof *arr * r);
if (arr)
for (i = 0; i < c; i++)
arr[i] = malloc(sizeof *arr[i] * c);
...
arr[i][j] = ...;
...
for (i = 0; i < c; i++)
free(arr[i]);
free(arr);
或使用一维偏移:
int *arr = malloc(sizeof *arr * r * c);
if (arr)
...
arr[i * r + j] = ...;
...
free(arr);
【讨论】:
【参考方案3】:VLA 在运行时在堆栈上分配数组,这使得确定编译时使用的堆栈大小变得更加困难,甚至不可能。由于堆栈的可用内存量相当小(与堆相比),许多人担心 VLA 有很大的堆栈溢出可能性。
即将发布的 MISRA-C 编码标准版本很可能也会禁止 VLA。
【讨论】:
不,可以轻松地在堆上创建 VLA。见***.com/a/54163435/4989451 @tstanisl 你误解了那个(主要是理论上的)帖子。在使用 malloc 的示例中,它们创建 一个指向 VLA 的指针,指向已分配的内存。然后将指针传递给接受 VLA 参数的函数。该参数衰减为指向 VLA 的指针。在这两种情况下都没有分配实际的 VLA。指向 VLA 的指针很好,但是使用 VLA 数组语法分配实际的 VLA 有点用处。请注意,指向 VLA 的指针与指向(静态)数组类型的指针一致且兼容。 发布此答案 8 年后,我们现在知道 C11 和 MISRA-C:2012 委员会都对 VLA 整体持怀疑态度。因此 C11 将它们设为可选,MISRA-C 完全禁止它们。两者都未能理解指向 VLA 的指针的用处,它们可用于创建更清晰、更安全的代码。 很大程度上取决于人们对 VLA 的理解。对象(读取数据)或可变大小的数组类型。无论如何,可以在 heap 上轻松创建 VLA 对象,如示例所示,其中指向 VLA 对象的指针被分配给带有malloc()
的 allocated VLA 对象。 VLA 不是关于存储(静态、自动、动态等),而是关于具有运行时定义大小的类型。
@tstanisl C17 6.7.6.2 数组声明器描述你声明一个可变长度类型:“如果一个标识符被声明为具有可变修改的类型,它应该是一个普通的标识符(如 6.2.3 中所定义) ),没有链接,并且具有块作用域或函数原型作用域。如果将标识符声明为具有静态或线程存储持续时间的对象,则它不应具有可变长度数组类型。"【参考方案4】:
VLA 使堆栈溢出变得更加容易。在您将使用 VLA 的大多数地方,您会将长度基于函数参数之一。如果参数是您不期望的,您最终可能会在堆栈上分配一个非常大的数组。除非您可以确定没有任何参数组合会导致堆栈溢出,否则您应该使用动态分配。
在嵌入式平台上使用它们可能有意义的一个地方,因为在进行嵌入式编程时,您可能会密切跟踪内存使用情况以确保不会有堆栈溢出。
【讨论】:
以上是关于C99 式 VLA 都有哪些技术缺点? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
与 malloc/free 相比,使用 C99 VLA 是个好主意吗?
Kafka 与 Apache Pulsar 相比都有哪些优缺点 [关闭]
Spring Boot 对于 Java Web 应用程序都有哪些缺点? [关闭]