例子1
我們先來看下面的一個例子:
#include <stdio.h>
int main(){
char str[10] = {0};
gets(str);
printf("str: %s\n", str);
return 0;
}
在 main() 函數內部定義一個字符數組,並通過 gets() 為它賦值。
debug
在VS2010 Debug模式下運行程序,當輸入的字符不超過10個時,可以正確輸出,但是當輸入的字符過多時,就會出現運行時錯誤。例如輸入"12345678901234567890",就會出現下面的錯誤:
這是為什么呢?我們不妨先來看一下 main() 函數的棧:
局部數組也是在棧上分配內存,當輸入"12345678901234567890" 時,會發生數組溢出,占用“4字節空白內存”、“old ebp”和“返回地址”所在的內存,並將原有的數據覆蓋掉,這樣當 main() 函數執行完成后,會取得一個錯誤的返回地址,該地址上的指令是不確定的,或者根本就沒有指令,所以程序在返回時出錯。
C語言不會對數組溢出做檢測,這是一個典型的由於數組溢出導致覆蓋了函數返回地址的例子,我們將這樣的錯誤稱為“棧溢出錯誤”。
注意:這里所說的“棧溢出”是指棧上的某個數據過大,覆蓋了其他的數據,和《棧(Stack)是什么?棧溢出又是怎么回事?》一節中提到的棧溢出不是一回事。
例子2
局部數組在棧上分配內存,並且不對數組溢出做檢測,這是導致棧溢出的根源。除了上面講到的 gets() 函數,strcpy()、scanf() 等能夠向數組寫入數據的函數都有導致棧溢出的風險。
下面是使用 strcpy() 函數導致棧溢出的例子:
#include <stdio.h>
#include <string.h>
int main(){
char *str1 = "這里是C語言啊";
char str2[6] = {0};
strcpy(str2, str1);
printf("str: %s\n", str2);
return 0;
}
將 str1 復制到 str2,顯然超出了 str2 的接受范圍,會發生溢出,覆蓋返回地址,導致 main() 函數返回時出錯。
棧溢出一般不會產生嚴重的后果,但是如果有用戶精心構造棧溢出,讓返回地址指向惡意代碼,那就比較危險了,這就是常說的棧溢出攻擊。