顧名思義,stack overflow 就是是棧溢位了。在進行數值運算時,我們常常要和運算結果的溢位打交道。數值運算結果可能上溢(overflow),也可能是下溢(underflow)。不過棧的溢位顯然只可能是上溢,即棧空間被用完了。在提起“棧”(stack)這個概念的時候,千萬不要忘記了它的兄弟“堆”(heap),也要切記不要把二者搞混了。
那麼,什麼時候會把給用完了呢?如果我們記得C程式中的區域性變數是在棧中分配的,函式呼叫會佔用一部分棧空間,則可以很容易地構造出相應的測試用例。
1、定義佔用空間過大的區域性變數所導致的棧溢位
C:\> more stack_local.c
/*
* Allocate too much memory from stack will cause stack overflow.
*/
#include <stdio.h>
int main(int argc, char *argv[])
{
int foo[1000000];
return 0;
}
C:\> cl stack_local.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
stack_local.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
/out:stack_local.exe
stack_local.obj
C:\> stack_local
此時出現一個異常對話方塊:stack-local.jpg 。
2、函式遞迴呼叫導致的棧溢位
C:\> more stack_recursive.c
* Infinite recursive calls will lead to stack overflow soon.
static void foo(void);
static void bar(void);
foo();
static void foo(void)
bar();
static void bar(void)
C:\> cl stack_recursive.c
stack_recursive.c
/out:stack_recursive.exe
stack_recursive.obj
C:\> stack_recursive
該程式沒聲沒息就結束了。檢視程序返回值能發現它其實是異常終止了。只不過沒有像 stack_local 那樣彈出一個對話方塊。
C:\> echo %errorlevel%
-1073741819
要搞清楚這兩個程式為什麼有這點細微的區別,可以查閱一下二者的彙編程式碼。原來是 _chkstk() 在起作用,其中 stack_local 在程式初始載入時就會導致 _chkstk() 失敗,觸發異常。而 stack_recursive 可以正確載入,並執行一段時間,然後導致棧溢位,並觸發異常。
要正確處理棧溢位採用以下辦法:
(1)修正我們的程式,不要造成無窮遞迴或太深的遞迴。我們可以把某些遞迴程式碼非遞迴化,例如那個經典的 qsort ,最好就用非遞迴的演算法來實現,就比較皮實一點。
(2)修正我們的程式,不要定義過大的區域性變數,特別是在定義大結構、大陣列時要格外小心。有時我們可能會用 _alloca() 這樣的特殊函式直接在棧上分配空間,更要多加註意。
(3)利用編譯器的特性,將程序允許的棧大小設定得大一些。例如可以採用 MSC 中的 /STACK 引數開關。
(4)對於那些還可能導致棧溢位的程式碼,採用 Microsoft 的結構化異常處理或標準的 C++ 異常處理機制,結合 _resetstkoflw() 進行處理。當然了,要是不嫌麻煩,我們也可以自己探測所用棧的大小,動態地檢測是否可能導致棧溢位,以避免可能的異常。
顧名思義,stack overflow 就是是棧溢位了。在進行數值運算時,我們常常要和運算結果的溢位打交道。數值運算結果可能上溢(overflow),也可能是下溢(underflow)。不過棧的溢位顯然只可能是上溢,即棧空間被用完了。在提起“棧”(stack)這個概念的時候,千萬不要忘記了它的兄弟“堆”(heap),也要切記不要把二者搞混了。
那麼,什麼時候會把給用完了呢?如果我們記得C程式中的區域性變數是在棧中分配的,函式呼叫會佔用一部分棧空間,則可以很容易地構造出相應的測試用例。
1、定義佔用空間過大的區域性變數所導致的棧溢位
C:\> more stack_local.c
/*
* Allocate too much memory from stack will cause stack overflow.
*/
#include <stdio.h>
int main(int argc, char *argv[])
{
int foo[1000000];
return 0;
}
C:\> cl stack_local.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
stack_local.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:stack_local.exe
stack_local.obj
C:\> stack_local
此時出現一個異常對話方塊:stack-local.jpg 。
2、函式遞迴呼叫導致的棧溢位
C:\> more stack_recursive.c
/*
* Infinite recursive calls will lead to stack overflow soon.
*/
#include <stdio.h>
static void foo(void);
static void bar(void);
int main(int argc, char *argv[])
{
foo();
return 0;
}
static void foo(void)
{
bar();
}
static void bar(void)
{
foo();
}
C:\> cl stack_recursive.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation. All rights reserved.
stack_recursive.c
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:stack_recursive.exe
stack_recursive.obj
C:\> stack_recursive
該程式沒聲沒息就結束了。檢視程序返回值能發現它其實是異常終止了。只不過沒有像 stack_local 那樣彈出一個對話方塊。
C:\> echo %errorlevel%
-1073741819
要搞清楚這兩個程式為什麼有這點細微的區別,可以查閱一下二者的彙編程式碼。原來是 _chkstk() 在起作用,其中 stack_local 在程式初始載入時就會導致 _chkstk() 失敗,觸發異常。而 stack_recursive 可以正確載入,並執行一段時間,然後導致棧溢位,並觸發異常。
要正確處理棧溢位採用以下辦法:
(1)修正我們的程式,不要造成無窮遞迴或太深的遞迴。我們可以把某些遞迴程式碼非遞迴化,例如那個經典的 qsort ,最好就用非遞迴的演算法來實現,就比較皮實一點。
(2)修正我們的程式,不要定義過大的區域性變數,特別是在定義大結構、大陣列時要格外小心。有時我們可能會用 _alloca() 這樣的特殊函式直接在棧上分配空間,更要多加註意。
(3)利用編譯器的特性,將程序允許的棧大小設定得大一些。例如可以採用 MSC 中的 /STACK 引數開關。
(4)對於那些還可能導致棧溢位的程式碼,採用 Microsoft 的結構化異常處理或標準的 C++ 異常處理機制,結合 _resetstkoflw() 進行處理。當然了,要是不嫌麻煩,我們也可以自己探測所用棧的大小,動態地檢測是否可能導致棧溢位,以避免可能的異常。