c - 为什么 scanf() 在填充数组时不生成内存破坏错误。

标签 c scanf

给定一个包含 5 个元素的数组,众所周知,如果您使用 scanf() 恰好读入 5 个元素,则 scanf() 将填充数组,然后通过将空字符 '\0' 放入其中来破坏内存第 6 个元素而不会产生错误(我称它为第 6 个元素,但我知道它的内存不是数组的一部分)如此处所述:Null termination of char array

但是,当您尝试读入 6 个或更多元素时,会生成一个错误,因为操作系统检测到内存正在被破坏并且内核会发送一个信号。有人可以弄清楚为什么在上面的第一种内存破坏情况下没有生成错误吗?

示例代码:

// ex1.c
#include <stdio.h>
int main(void){
  char arr[5];
  scanf("%s", arr);
  printf("%s\n", arr);
  return 0;
}

编译、运行并输入四个字符:1234。这会将它们正确地存储在数组中并且不会破坏内存。这里没有错误。

$ ./ex1
1234
1234

再次运行,输入五个字符。这会破坏内存,因为 scanf() 在第 5 个元素之后在内存中存储了一个额外的 '\0' 空字符。不会产生错误。

$ ./ex1
12345
12345

现在输入六个我们预计会破坏内存的字符。生成的错误看起来像(即我猜)它是内核发送的信号的结果,说我们刚刚以某种方式破坏了堆栈(本地内存)......为什么会为此内存破坏生成错误但是不是上面的前一个吗?

$ ./ex1
123456
123456
*** stack smashing detected ***: ./ex1 terminated
Aborted (core dumped)

无论我制作多大的阵列,这似乎都会发生。

最佳答案

行为是 undefined如果在这两种情况下您输入的字符多于缓冲区可以容纳的字符数。

堆栈粉碎检测机制通过使用 canaries 起作用.当 canary 值被覆盖时,会生成 SIGABRT。它没有生成的原因可能是因为数组后至少有一个额外的内存字节(通常对象的末尾后一个是有效指针。但它不能使用存储到值——合法)。 本质上,当您输入 1 个额外的字符时,金丝雀不会被覆盖,但是当您出于某种原因输入 2 个字节时,它确实会被覆盖,从而触发 SIGABRT。

如果在arr之后还有一些其他的变量比如:

#include <stdio.h>
int main(void){
  char arr[5];
  char var[128];
  scanf("%s", arr);
  printf("%s\n", arr);
  return 0;
}

然后,当您输入更多字节时,金丝雀可能不会被覆盖,因为它可能只是覆盖 var。从而延长了编译器对缓冲区溢出的检测。这是一个似是而非的解释。但在任何情况下,如果您的程序溢出缓冲区,它就是无效的,您不应该依赖编译器的堆栈粉碎检测来拯救您。

关于c - 为什么 scanf() 在填充数组时不生成内存破坏错误。,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31764317/

相关文章:

c++ - 替换文件中的单词

将多字符数组复制到另一个多字符数组

c - 各自领域的学习结构和算术

sscanf可以用来匹配通配符吗?

c - 如何重置 scanf()

c - 如何提高 C 中大数据排序的执行速度

c - 反向打印链表

c++ - 检查 C 中 int 和 float 之间的相等性

使用 struct 和 scanf 的 C 程序产生错误 : expected expression before ‘ROBOT’

c++ - 循环错误试图验证有效输入以在 C++ 中加倍