在Visual Studio 2012控制台应用程序中执行以下程序时:
#include <stdio.h>
int main() {
int integer1, integer2, sum;
char str[5];
scanf("%s",str); /* Try to enter 10 chars */
printf("%s\n",str);
printf( "Enter first integer\n" );
scanf( "%d", &integer1 );
printf( "Enter second integer\n" );
scanf( "%d", &integer2 );
sum = integer1 + integer2;
printf( "Sum = %d\n", sum );
return 0;
}
它抛出异常“StackOverFlow”,这很明显,因为语句:
scanf("%s",str); /* Try to enter 10 chars */
我的问题是:为什么程序继续执行(通过打印str字符串,要求输入2个整数,求它们并打印结果),即使异常应该更早发生?
答案 0 :(得分:3)
堆栈从高地址到低地址逐渐减少。一个CPU寄存器,堆栈指针跟踪堆栈的顶部 - 实际上是在最低地址,因为堆栈朝着较低的地址增长。编译器会查看您的函数(在本例中为main),并查看它需要多少自动存储,即存储局部变量。它生成代码以将堆栈指针减少函数所需的本地存储量。当函数被调用时,调用者将堆栈上的返回地址(递减堆栈指针)推送到堆栈,然后分支到被调用的函数,后者又递减堆栈指针(创建堆栈帧)地方变量的空间。
如果某个程序溢出了局部变量(就像你的那样),很可能会丢弃返回地址。由于堆栈向向向低位地址增长,因此写入堆栈帧(朝向更高地址)将覆盖旧堆栈帧(调用方和调用方的调用方等)。
虽然main()是程序中要调用的第一个函数,但是已经有一个活动的堆栈帧,对应于main()的调用者,它是运行时环境。
在您的函数 main()尝试返回之前,不会注意到垃圾堆栈的任何副作用(如覆盖返回地址)。然后会发生什么事是任何人的猜测。如果返回地址被一个指向堆栈中某个位置的值覆盖,那么CPU将在那里进行分支,这是恶意代码的经典利用,利用缓冲区溢出和堆栈上分配的缓冲区。
这些链接有助于理解基于堆栈的缓冲区溢出:
http://www.tenouk.com/Bufferoverflowc/Bufferoverflow3.html http://en.wikipedia.org/wiki/Format_string_attack
最近的微处理器提供了一种防止数据执行的安全功能,一旦程序尝试返回指向数据的损坏地址(如堆栈),CPU就会引发异常。
http://en.wikipedia.org/wiki/NX_bit
答案 1 :(得分:1)
因为C不检查所有内容(什么?)。你的长字符串已经在堆栈上乱写了,当函数返回时,会注意到堆栈损坏。
值得注意的是,应始终使用safe versions of scanf
类型函数。
答案 2 :(得分:1)
在C中,代码不能抛出异常。此外,scanf()
不会检查堆栈。
可能发生的是Visual Studio为您的程序创建环境,包括设置堆栈。虽然它这样做,但它用一种模式填充堆栈。
当main()
返回时,将检查模式。只有在那个时候,C运行时才会注意到你已经破坏了堆栈。
结论:切勿使用scanf()
和sprintf()
的不安全版本。运行时可能会捕获错误但是它会做得太晚,即使你收到错误消息,也不会帮助你找到它发生的时间。