【问题标题】:C tiny, pre-allocated array does not overrunC 微小的、预分配的数组不会溢出
【发布时间】:2011-07-20 15:13:17
【问题描述】:

我预计这段代码会出现段错误:

char * foo (char my_ascii[10])
{
  strcpy (my_ascii, "0123456789");

  return my_ascii;
}

char bar[2];

printf("%s\n", foo (bar));

因为 bar 在堆栈中保留了 2 个字符的数组,而 foo() 尝试写入 10 个字符。但是, printf() 写入标准输出 10 个字符并且不会发生错误。为什么会这样?

另外,如果我这样修改 foo() 函数:

char * foo (char my_ascii[1])
{
  strcpy (my_ascii, "0123456789");

  return my_ascii;
}

行为完全相同:将 10 个字符复制到 my_ascii。有什么解释吗?

非常感谢您。

【问题讨论】:

  • 我不相信你可以在函数声明中分配内存(所以char *func(char x[10])char *func(char *x)是一样的
  • 复制了 11 个字符,而不是 10 个! strcpy ever 会将终止的 '\0' 复制到目标 --> 未定义的行为。

标签: c arrays string overflow overrun


【解决方案1】:

指定数组参数的长度如

char * foo (char my_ascii[1]) ...

没有任何区别,因为它被省略了(数组衰减为函数内部的指针)。

此外,缓冲区溢出是未定义的行为,这意味着:不能保证程序会崩溃。它可能完全合法地看起来好像没有问题...或仅在周四满月时生成段错误...或默默地从您的数据库中删除所有记录。真的,什么都行。

【讨论】:

  • 不要忘记将“nasal demons”作为未定义行为的可能结果。
【解决方案2】:

首先,这些定义是完全相同的:

char *foo1(char arr[10]) { /* ... */ }
char *foo2(char arr[1]) { /* ... */ }
char *foo3(char arr[]) { /* ... */ }
char *foo4(char *arr) { /* ... */ }

其次,写在对象的限制之外是未定义的行为。任何事情都有可能发生!如果你很幸运,你的测试运行会崩溃,你会做对的;如果你不是那么幸运,你的测试运行会像你预期的那样工作,只有在你向客户(或你的老板)演示时才会失败。

【讨论】:

    【解决方案3】:

    char * foo (char my_ascii[10])char * foo (char my_ascii[1]) 都等价于char * foo (char *my_ascii)

    注意:数组类型在传递给函数时会衰减为指针(指向数组的第一个元素)类型。

    因为bar 在堆栈中保留了一个 2 字符数组,而foo() 尝试写入 10 个字符。但是,printf() 写入标准输出 10 个字符并且不会发生错误。为什么会这样?

    这是因为未定义的行为意味着任何事情都可能发生。

    仅供参考

    未定义行为是指在使用不可移植或错误程序结构或错误数据时的行为,本国际标准对此没有任何要求

    注意:可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(无论是否发出诊断消息) ),终止翻译或执行(通过发出诊断消息)。

    【讨论】:

      【解决方案4】:

      不幸的是,未定义的行为意味着任何事情都可能发生 - 包括没有错误症状。在这种情况下,您覆盖了堆栈的一部分,但它没有影响任何东西。

      【讨论】:

        【解决方案5】:

        bar 确实保留了 2 个字符,而您填充的字符超出了它的处理能力 8 个。

        这并不意味着段错误。

        你不知道那些溢出的 8 个字符是什么,很可能是可以安全覆盖的无意义垃圾。当您实际覆盖虚拟内存的另一页或覆盖重要的东西(如设备驱动程序或程序代码)时,会发生段错误。

        这是未定义行为的一个很好的例子。未定义并不意味着它失败,它确实意味着行为是未定义;它可能会起作用,它可能会失败,猴子可能会从 USB 端口飞出……任何事情都可能发生。在这种情况下,它确实有效,但您不能依赖这种行为,因为下次运行程序时它可能会有所不同。

        最后,仅仅因为没有立即发生故障,并不意味着您没有损坏系统。你可能已经用你的覆盖搞砸了内存,直到在你的程序中你可能看不到它,当它突然在完全正常的代码上崩溃时,恰好依赖于相同的内存区域。


        顺便说一句:您的代码中还有另一个错误。
        您将 my_ascii 描述为 10 个字符,但您尝试将 11 个字符复制到其中。
        不要忘记字符串末尾的 NULL 终止符!
        这意味着字符串 "0123456789" 实际上需要 11 个字符的存储空间。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-08-30
          • 1970-01-01
          • 2022-01-04
          • 1970-01-01
          • 2018-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多