我期待使用此代码进行段错:
char * foo (char my_ascii[10])
{
strcpy (my_ascii, "0123456789");
return my_ascii;
}
char bar[2];
printf("%s\n", foo (bar));
因为bar在堆栈中保留了2-char数组,而foo()尝试写入10个字符。但是,printf()在stdout 10个字符中写入并且不会发生错误。为什么会这样?
此外,如果我以这种方式修改foo()函数:
char * foo (char my_ascii[1])
{
strcpy (my_ascii, "0123456789");
return my_ascii;
}
行为完全相同:将10个字符复制到my_ascii。有什么解释吗?
非常感谢你。
答案 0 :(得分:2)
指定数组参数的长度,如
char * foo (char my_ascii[1]) ...
没有任何区别,因为它被省略(数组衰变成函数内的指针)。
此外,缓冲区溢出是未定义的行为,这意味着:不能保证程序会崩溃。它可能完全合法看起来好像没有问题...或仅在周四有满月时生成段错误...或者无声地删除数据库中的所有记录。真的,任何事情。
答案 1 :(得分:1)
char * foo (char my_ascii[10])
和char * foo (char my_ascii[1])
都等同于char * foo (char *my_ascii)
注意:传递给函数时,数组类型会衰减为指针(指向数组的第一个元素)类型。
因为
bar
在堆栈中保留了2-char数组,而foo()
尝试写入10个字符。但是,printf()
写入stdout 10个字符并且不会发生错误。为什么会这样?
那是因为未定义的行为意味着任何事情都可能发生。
仅供记录
未定义的行为表示在使用不可移植或错误的程序构造或错误数据时的行为,本国际标准不对其施加任何要求
注意:可能的未定义行为范围从完全忽略具有不可预测结果的情况,在翻译或程序执行期间以文档特有的环境行为(有或没有发出诊断消息) ),终止翻译或执行(发布诊断信息)。
答案 2 :(得分:1)
首先,这些定义完全相同:
char *foo1(char arr[10]) { /* ... */ }
char *foo2(char arr[1]) { /* ... */ }
char *foo3(char arr[]) { /* ... */ }
char *foo4(char *arr) { /* ... */ }
其次,在对象的限制之外写作是未定义的行为。一切都会发生!如果你很幸运,你的测试运行会崩溃,你会做对的;如果你没那么幸运,那么当你向客户(或你的老板)演示它时,你的测试运行将会如你所期望的那样失败。
答案 3 :(得分:0)
bar
确实保留了2个字符,并且你要填充8个字符,而不是它可以处理的字符。
这并不意味着会出现seg-fault。
你不知道那些过度流过的8个字符是什么,并且它可能是无意义的垃圾,可以安全地覆盖。当您实际覆盖到另一个虚拟内存页面或覆盖重要内容(如设备驱动程序或程序代码)时,会发生seg-fault。
这是未定义行为的一个很好的例子。未定义并不意味着 WILL 失败,这实际上意味着行为未定义;它可能会工作,它可能会失败,猴子可能会飞出USB端口......任何事情都可能发生。在这种情况下,它实际上有效,但您不能依赖于该行为,因为下次运行程序时它可能会有所不同。
最后,仅仅因为没有立即失败,这并不意味着你没有损坏系统。你可能已经用你的覆盖搞砸了你的内存,你可能直到你的程序更进一步看到它,它突然崩溃在完全正常的代码上,恰好依赖于相同的内存区域。
BTW:您的代码中还有另一个错误
您将my_ascii
描述为10个字符,但您尝试将11个字符复制到其中
不要忘记字符串末尾的NULL终结符!
这意味着字符串"0123456789"
实际上需要11个字符的存储空间。
答案 4 :(得分:0)
不幸的是,未定义的行为意味着任何事情都可能发生 - 包括没有错误的症状。在这种情况下,你覆盖了堆栈的一部分,但它没有影响任何东西。