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

时间:2015-08-01 17:29:52

标签: c scanf

给定一个包含5个元素的数组,众所周知,如果你使用scanf()来读取5个元素,那么scanf()将填充数组,然后通过将空字符'\ 0'放入clobber内存第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'null字符。没有生成错误。

$ ./ex1
12345
12345

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

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

无论我制作数组的大小,这似乎都会发生。

4 个答案:

答案 0 :(得分:1)

  

。为什么这个内存崩溃会产生错误,但上面的错误却没有?

因为第一次测试似乎只是因为(坏)运气而起作用。

在这两种情况下,arr都被访问了越界,并且通过这样做,代码调用了未定义的行为。这意味着代码可以执行您期望与否的操作,或启动计算机,格式化磁盘......

C不测试内存访问,但将其留给程序员。谁可以通过以下方式拨打电话scanf()

char arr[5];
scanf("%4s", arr); /* Stop scanning after 4th character. */

答案 1 :(得分:1)

如果在输入的字符多于缓冲区可以容纳的字符数的情况下,行为为undefined

堆栈粉碎检测机制使用canaries。当金丝雀值被覆盖时,生成SIGABRT。之所以没有生成它可能是因为在数组之后至少有一个额外的内存字节(通常需要一个对象的结尾是一个有效的指针。但它不能被使用存储到价值 - 合法地)。 本质上,当您输入1个额外的char时,canary不会被覆盖,但是当您因某种原因输入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。从而延长了编译器的缓冲区溢出检测。这是一个似是而非的解释。但无论如何,如果程序超出缓冲区,你的程序无效,你不应该依赖编译器的堆栈粉碎检测来保存你。

答案 2 :(得分:0)

Stack Smashing这里实际上是由于编译器用来检测缓冲区溢出错误的保护机制引起的。编译器添加了具有已知值的保护变量(称为canary)。

如果大小超过5的输入字符串导致此变量损坏,导致SIGABRT终止程序。

您可以阅读有关buffer overflow protection的更多信息。但正如@alk所说,你正在调用Undefined Behavior

答案 3 :(得分:0)

实际上 如果我们声明一个大小为5的数组,那么我们也可以放置和访问来自这个数组的数据,因为超出这个数组的内存是空的,我们可以做同样的事情,直到这个内存是空闲的,但是一旦它被分配到另一个程序,现在即使我们是无法访问那里的数据