分配超大堆栈结构是不确定的行为?

nne*_*neo 25 c c++ undefined-behavior language-lawyer

这是一个C规范问题.

我们都知道这是合法的C,应该可以在任何平台上正常工作:

/* Stupid way to count the length of a number */
int count_len(int val) {
    char buf[256];
    return sprintf(buf, "%d", val);
}
Run Code Online (Sandbox Code Playgroud)

但这几乎可以保证崩溃:

/* Stupid way to count the length of a number */
int count_len(int val) {
    char buf[256000000];
    return sprintf(buf, "%d", val);
}
Run Code Online (Sandbox Code Playgroud)

不同之处在于后一个程序会破坏堆栈并且可能会崩溃.但是,纯粹在语义上,它与以前的程序没有任何不同.

根据C规范,后一个程序实际上是未定义的行为吗?如果是这样,它与前者的区别是什么?如果不是,那么C规范中的说法是否可以使符合要求的实现崩溃?

(如果这在C89/C99/C11/C++*之间有所不同,那么这也很有趣).

amd*_*mdn 5

语言标准C(899911)与此措词(在一些C还发现++,C#,Fortran和帕斯卡标准)一个范围段开始:

本国际标准未规定

  • 程序及其数据的大小或复杂性将超过任何特定数据处理系统的容量或特定处理器的容量;
  • 能够支持符合标准的实现的数据处理系统的所有最低要求。

gcc编译器确实提供了在运行时检查堆栈溢出的选项

21.1堆栈溢出检查

对于大多数操作系统,默认情况下,gcc不会执行堆栈溢出检查。这意味着,如果主环境任务或某些其他任务超出了可用的堆栈空间,则将发生不可预测的行为。大多数本机系统通过在每个任务堆栈的末尾添加保护页面来提供某种程度的保护。这种机制通常不足以正确处理堆栈溢出情况,因为较大的局部变量可能会“跳到”保护页面上方。此外,当命中保护页时,堆栈上可能没有剩余空间可用于执行异常传播代码。启用堆栈检查可以避免这种情况。要激活堆栈检查,请使用gcc选项-fstack-check编译所有单元。例如:

gcc -c -fstack-check package1.adb
Run Code Online (Sandbox Code Playgroud)

使用此选项编译的单元将生成额外的指令,以检查对堆栈的任何使用(用于过程调用或在声明块中声明局部变量)是否不超过可用的堆栈空间。如果超出空间,则会引发Storage_Error异常。

在标准化过程中,C99曾试图在标准中做出更强有力的声明,即规模和复杂性超出了标准范围,但实施者有责任记录这些限制。

理由是

一致性的定义一直是C标准的一个问题,被一位作者描述为“甚至没有橡胶齿,更像是橡胶胶”。尽管与C89相比C9X有所改进,但许多问题仍然存在。

本文提出了一些变化,这些变化虽然不完美,但有望改善这种情况。

建议将以下措词纳入第5.2.4.1节

  • 如果程序或其数据的大小或复杂性超出实现的能力,则转换或执行可能会失败。
  • 实施应记录确定正确程序的大小或复杂性是否超过或可能超过实施能力的方法。

    5.2.4.1。实现始终可以自由声明给定程序太大或太复杂而无法翻译或执行。但是,要在没有提供任何有用功能的同时停止要求一致性的一种方法,实现者必须提供一种确定程序是否可能超出限制的方法。该方法不一定是完美的,只要它在谨慎方面出错即可。一种实现方法是拥有一个公式,该公式将诸如变量数之类的值转换为编译器所需的内存量。同样,如果堆栈空间有限制,该公式仅需要显示如何确定每个函数调用的堆栈要求(假设这是分配堆栈的唯一位置),并且不需要遍历所有可能的执行路径(面对递归,这是不可能的)。编译器甚至可以具有为程序中的每个功能输出值的模式。

提议的措词未纳入C99标准,因此该领域不在标准范围之内。C99的5.2.4.1节确实列出了这些限制

该实现应能够翻译和执行至少一个程序,该程序包含以下每个限制的至少一个实例:

  • 127个块的嵌套级别
  • 63个条件包含的嵌套级别
  • 12个指针,数组和函数声明符(任意组合),用于修改声明中的算术,结构,联合或不完整类型
  • 完整声明符中带括号的声明符的63个嵌套级别
  • 完整表达式中带括号的表达式的63个嵌套级别
  • 内部标识符或宏名称中的63个有效初始字符(每个通用字符名称或扩展源字符均视为单个字符)
  • 外部标识符中的31个有效初始字符(每个指定短标识符0000FFFF或更少的通用字符名称被认为是6个字符,每个指定短标识符00010000或更多的通用字符名称被认为是10个字符,并且每个扩展源字符都被视为与相应的通用字符名称(如果有)相同的字符数)
  • 一个翻译单元中包含4095个外部标识符
  • 在一个块中声明了511个具有块范围的标识符
  • 在一个预处理翻译单元中同时定义4095个宏标识符
  • 一个功能定义中包含127个参数
  • 一个函数调用中包含127个参数
  • 一个宏定义中包含127个参数
  • 一次宏调用中包含127个参数
  • 逻辑源代码行中的4095个字符
  • 字符串文字或宽字符串文字中的4095个字符(串联后)
  • 对象中的65535字节(仅在托管环境中)
  • #include文件的15个嵌套级别
  • switch语句的1023个案例标签(不包括任何嵌套switch语句的案例标签)
  • 单个结构或联合中的1023个成员
  • 单个枚举中的1023枚举常量
  • 单个struct-declaration-list中的63个嵌套结构或联合定义级别