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

给定一个包含5个元素的数组,众所周知,如果使用scanf()读取5个元素,则scanf()将填充数组,然后通过将空字符’\ 0’放入第6个元素来破坏内存没有产生错误(我称之为第6个元素,但我知道它的内存不属于数组)如下所述: char数组的空终止

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

示例代码:

// ex1.c #include  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) 

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

如果在输入的字符数多于缓冲区可容纳的字符数的情况下,则行为未定义

堆栈粉碎检测机制通过使用金丝雀来工作 。 当金丝雀值被覆盖时,生成SIGABRT。 它没有生成的原因可能是因为在数组之后至少有一个额外的内存字节(通常需要一个对象的一端接一个有效的指针。但它不能使用存储到价值 – 合法地)。 本质上,当您输入1个额外的char时,canary不会被覆盖,但是当您因某种原因输入2个字节时它会被覆盖,从而触发SIGABRT。

如果在arr之后还有其他一些变量,例如:

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

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

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

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

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

C不测试内存访问,但将其留给程序员。 谁可以通过执行以下操作来调用scanf()

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

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

在您的情况下,大小大于5的输入字符串会导致此变量损坏,从而导致SIGABRT终止程序。

您可以阅读有关缓冲区溢出保护的更多信 但是@alk回答你正在调用Undefined Behavior

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