1

我期待这段代码出现段错误:

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。有什么解释吗?

非常感谢您提前。

4

5 回答 5

2

指定数组参数的长度,例如

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

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

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

于 2011-03-22T16:08:57.133 回答
1

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

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

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

于 2011-03-22T16:10:33.547 回答
1

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

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

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

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

只是为了记录

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

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

于 2011-03-22T16:07:38.083 回答
0

确实bar保留了 2 个字符,并且您填充的字符超出了它可以处理的 8 个字符。

这并不意味着段错误。

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

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

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


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

于 2011-03-22T16:08:19.190 回答
0

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

于 2011-03-22T16:09:27.077 回答