【问题标题】:Why does C not define minimum size for an array?为什么 C 没有定义数组的最小大小?
【发布时间】:2013-01-19 15:43:23
【问题描述】:

C 标准定义了许多下限/上限 (translation limits),并规定了每个翻译都应满足的实现。为什么没有为数组大小定义这样的最小限制?以下程序可以正常编译,并可能产生运行时错误/段错误,并会调用 未定义行为

int main()
{
   int a[99999999];
   int i;

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

   return 0;
}

一个可能的原因可能是在自动存储上分配了本地数组,这取决于分配的堆栈帧的大小。但是为什么不像 C 定义的其他限制那样设置最小限制呢?

让我们忘记上面的未定义情况。考虑以下几点:

int main()
{
   int a[10];
   int i;

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

   return 0;
}

在上面,是什么让我保证本地数组(尽管非常小)会按预期工作并且不会由于分配失败而导致未定义的行为?

尽管在任何现代系统上分配如此小的阵列都不太可能失败。但是 C 标准没有定义任何要满足的要求,并且编译器不会(至少 GCC 不会)报告分配失败。只有运行时错误/未定义的行为是可能的。 困难的部分是没有人知道任意大小的数组是否会由于分配失败而导致未定义的行为。

请注意,我知道我可以为此目的使用动态数组(通过 malloc 和朋友),并且可以更好地控制分配失败。我更感兴趣的是为什么没有为本地数组定义这样的限制。此外,全局数组将存储在静态存储中,并将增加编译器可以处理的可执行文件大小。

【问题讨论】:

  • “困难的部分是没有人知道任意大小的数组是否会由于分配失败而导致未定义的行为。” - 你建议如何解决这个问题?你会使用什么最小值?请记住 C 运行的平台种类繁多,从带有少量堆栈的微型微控制器到带有非常大堆栈的 64 位以上架构,应有尽有。此外,递归会极大地影响堆栈的可用性。如果您使用 C 进行编程,您通常会了解您的硬件,从而了解您的堆栈可用性。
  • @KingsIndian 我认为解决方案是避免在可能对其施加意外压力的情况下使用“堆栈” - 并了解执行要求的限制。这有点像过多的递归:它会在某些环境中比其他环境更快地产生 UB。
  • @KingsIndian 写得很好。我试图想一个更好的标题,但你的最后一段消除了这样的焦点:) 希望它会保持开放,因为我认为有很好的信息和响应。

标签: c arrays undefined-behavior


【解决方案1】:

因为 C,语言,不应该对可用堆栈大小施加限制。 C 在许多(许多)不同的环境中运行。它怎么可能得出一个合理的数字?地狱,自动存储持续时间!=堆栈,堆栈是一个实现细节。 C,语言,没有提到“堆栈”。

环境决定了这些东西,这是有充分理由的。如果某个环境通过没有这种限制的替代方法实现自动存储持续时间怎么办?如果硬件出现突破,突然间现代机器不需要这样的限制怎么办?

我们应该在这种情况下修订标准吗?如果 C,语言,我们将不得不指定这样的实现细节。

【讨论】:

    【解决方案2】:

    您已经回答了自己的问题;这是由于堆栈限制。* 即使这样也可能不起作用:

    void foo(void) {
        int a;
    
        ...
    }
    

    如果... 实际上是对foo 的递归调用。

    换句话说,这与数组无关,因为同样的问题会影响所有局部变量。该标准无法强制执行要求,因为在实践中这将转化为对无限大小堆栈的要求。


    * 是的,我知道 C 标准不讨论堆栈。但这是隐含的模型,从某种意义上说,标准实际上是当时存在的实现的形式化。

    【讨论】:

    • 但是,他问的不是这个
    • 此外,堆栈从来都不是用来存放大件物品的地方。它适用于相对较小的事物,而大事物则适用于堆。因此,如果您尝试在堆栈上分配巨大的数组,那么您只是使用了错误的工具来完成这项工作。
    • @OliCharlesworth :他在问为什么该标准不需要实现来支持特定的最小数组大小才能符合要求。
    • @SanderDeDycker:我认为我的回答会立即出现!也就是说,它需要一个实现来提供无限堆栈以保持合规性。
    • @KingsIndian:我认为这确实是一个编译时间限制;即编译器在中断之前必须能够跟踪多少嵌套。
    【解决方案3】:

    最小限制是一个包含 1 个元素的数组。为什么你会有一个“限制”呢?当然,如果你永远递归调用一个函数,一个 1 的数组可能不适合堆栈,或者调用函数的调用下一次调用可能不适合堆栈 - 解决这个问题的唯一方法是知道编译器中堆栈的大小 - 但在那个阶段编译器实际上并不知道堆栈有多大 - 别介意极其复杂的调用层次结构的问题是几个不同的函数调用同一个函数,可能使用递归和/或几层相当大的堆栈消费者 - 你如何调整堆栈的大小 - 可能永远不会遇到最坏的情况,因为其他事情表明这不会发生 - 例如,一个函数中的最坏情况只是当输入文件为空时,但另一个函数中最坏的情况是同一文件中存储了大量数据。很多很多这样的变化。这太不可靠了,所以迟早会变成猜测或大量误报。

    考虑一个包含数千个函数的程序,所有这些函数都调用相同的日志记录函数,该函数需要堆栈上的 200 字节数组来临时存储日志输出。从 main 向上的几乎每个函数都调用它。

    局部变量的最大值取决于堆栈的大小,就像我上面所说的,编译器在编译代码时并不知道[链接器可能知道,但稍后再说]。对于全局数组和在堆上分配的数组,限制是“您的进程可以获得多少内存”,因此那里没有上限。

    没有简单的方法来确定这一点。该标准提供的许多限制是为了保证代码可以在“任何编译器”上编译,只要您的代码遵循规则。被编译和能够运行完成是两件不同的事情。

    int main() { 而(1); }

    永远不会运行完成——但它会在我所知道的每个编译器中编译,而且大多数人不会说存在无限循环——这是你的选择。

    您也可以选择将大型数组放入堆栈。很可能链接器被赋予了几千兆字节的堆栈,在这种情况下它会很好 - 或者堆栈是 200K,你不能有 50000 个整数数组......

    【讨论】:

    • 127 个嵌套级别可能根本不需要任何内存(除了代码空间)。如果你做了很多嵌套的 if/while/switch 语句,它们不会占用任何堆栈空间,除非它们中有局部变量。显然,大量的嵌套 if 和 while 等意味着必须使用一些数据,但不一定要使用很多。同样,该限制是为了保证如果您编写的代码(不超过)127 级,它可以在任何(兼容的)编译器上编译。代码可能不适合系统拥有的 8KB RAM,但编译器无能为力...
    猜你喜欢
    • 1970-01-01
    • 2017-07-23
    • 1970-01-01
    • 2020-03-06
    • 2017-07-27
    • 2023-03-08
    • 2010-12-26
    • 1970-01-01
    相关资源
    最近更新 更多