棧內存管理

21.2  棧內存管理

棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。在執行函數時,函數內局部變量的存儲單元都能夠在棧上創建,函數執行結束時這些存儲單元會自動被釋放。

21.2.1  申請棧內存

棧內存有兩種實現方法,一種是由系統根據需要自動分配,程序不能控制,另一種是用堆來模擬棧的操作。下面通過兩個例子來看一下棧空間是如何分配的。

【示例21-2】 函數f是遞歸函數,當從第n層進入第n+1層時就需要在棧上存儲現場。

int fun(int x)

{

    if x>0 then

        fun(x--);

cout<<"x="<<x<<endl;

    return 0;

}

分析:當x爲正數時就進入下一層遞歸,否則輸出x的值,然後退到上一層。當每進入一層遞歸時,任何其他返回時要恢復的現場數據都將被保存在棧上,例如x的值,返回後繼續執行的下一條指令的地址等。由於從內層遞歸返回外層後,原來的x的值還要使用,所以進入內層遞歸時,x的值必須保存在棧上。當返回時,再依次從棧上取出。

【示例21-3】 臨時變量保存在棧上的情況。

void main ()

{

    int a;

    float b;

    double c;

    char s[10];

    ...

}

void func(int x, int y)

{

    ...

}

分析:函數main()中聲明瞭4個臨時變量,各個變量在編譯時自動從棧上獲得存儲空間。各語句的意思如下所示。

  ● int a;表示系統在棧上爲整型變量a申請了int字節大小的內存存儲單元。

  ● float b;表示系統在棧上爲浮點型變量b申請了float字節大小的內存存儲單元。

  ● double d;表示系統在棧上爲雙精度型變量c申請了double字節大小的內存存儲單元。

  ● char s[10];表示系統在棧上爲字符型數組s 申請了10個char字節大小的內存存單元。

  ● 在函數func中的參數列表(int x, int y),申請了2個形參變量。

  ● int x表示系統在棧上爲形參x申請了int字節大小的內存存儲單元。

  ● int y表示系統在棧上爲形參y申請了int字節大小的內存存儲單元。

%注意:如果在棧上申請的內存大於棧上的所有剩餘的內存空間,系統將會提示棧溢出的       錯誤。

【示例21-4】 用指針來模擬的鏈棧。

class intstack

{

private:

    struct tagint

    {

        int x;

        int *p;

    } value;

public:

    void push(int y);

    int pop();

}

分析:該類是用堆來模擬了一個先進後出的棧。其中,結構體struct tagint實現了一個鏈棧,它的內存空間實際上來自於堆(下一節會講到),而函數push()和pop()則負責對該棧進行操作。push()是壓棧,既寫數據進棧,而pop()是從棧內彈出數據,同時釋放申請的空間。該示例實際上是不完整的,只說明了棧的基本元素,21.2.2節將補全該示例。關於鏈棧還有很多操作,可以參考本書其他章節。

21.2.2  使用棧內存

由於棧是由系統來管理,所以不會直觀地感覺到在使用棧,除非程序自己來模擬一個棧。

【示例21-5】 局部變量自動從棧上獲得存儲空間。

void main()

{

    int a;          //在棧上分配空間

    int b;          //在棧上分配空間

    int c;          //在棧上分配空間

    a = 25;         //賦值

    b = 68;         //賦值

    c = a + b;      //使用 a, b對c賦值

    std::cout <<  "the value of a + b is : "  <<  c;   //使用c

}

分析:該示例有3個局部變量,都從棧上獲得內存空間。每個變量的名字與一個棧上空間相對應。由於棧由系統來管理,所以使用時程序沒有特別需要注意的地方。因此使用棧時,只需給出對應的變量名即可。

【示例21-6】 補全了示例21-4,演示了用指針鏈模擬棧的方法,以說明棧的操作原理。

class intstack

{

public:

    intstack();

    ~intstack();

private:

    struct tagint

    {

        int x;              //保存數據

        struct tagint *p;   //指向下一個節點

    } *value;

public:

    void push(int y);       //壓棧

    int pop();              //出棧

    int gettop();           //得到棧頂元素的值

}

intstack::intstack()

{

    value=(struct tagint *)malloc(sizeof(struct tagint));   //申請內存空間

}

intstack::~intstack()

{

    //釋放

    struct tagint *st;

    st=value->p;

    while(st!=null)

    {

        value->p=st->p;                 //指向下一個位置

        free(st);                       //釋放

        st=value->p;                    //指向下一個位置

    }

    free(value);

}

//壓棧

void intstack::push(int y)

{

    struct tagint *st;

    st=(struct tagint *)malloc(sizeof(struct tagint));      //申請內存空間

    st->x=y;                            //賦值

    st->p=value->p;                     //指向當前鏈頭

    value->p=st;                        //鏈到棧頭

}

//得到棧頂元素的值

int intstack:: gettop ()

{

    return value->p->x;                 //返回棧頂元素的值

}

//返回棧頂元素的值,同時刪除棧頂

int intstack::pop()

{

    int y=value->p->x;                  //棧頂值

    struct tagint *st;

    st=value->p;                        //棧頂元素

    value->p=value->p->p;               //下一個元素成爲棧頂

    free(st);                           //釋放

    return y;                           //返回棧頂元素的值

}

分析:該示例是個簡單的用鏈模擬棧的類,從它使用內存的方式來看不是棧空間,而是動態分配空間。它用一個鏈來模擬一個先進後出的棧,用push()模擬壓棧,gettop()模擬取棧頂元素,pop()在取棧頂元素的同時刪除當前棧頂。這個示例演示了棧的基本操作原理,讓讀者對棧的使用有一個直觀的理解。

21.2.3  釋放棧內存

棧空間在不使用時要釋放掉。由於棧一般都是系統在管理,所以棧的釋放不用程序員來處理。當某個變量的生命期結束時,系統會自動釋放該變量所佔用的空間。如果是從內層遞歸返回,系統會自動釋放內層遞歸所申請的空間,彈出上層遞歸保留的變量和一些其他的參數。這些處理都在系統內進行,編寫程序時不需要考慮。

在21.2.2節的示例21-6中,由於棧是模擬的,所以分配和釋放都要由程序本身來處理。該模擬棧的空間釋放是在析構函數內,必須釋放所有棧空間,否則會浪費內層。value不是棧的節點,而是保存了指向棧頂的指針,也必須釋放,否則也會浪費內層。

21.2.4  改變大小

要改變棧上分配的內存大小,可以用C++的庫函數的alloca()函數,其原型如下:

void *alloca(size_t size);

這個函數在調用它的函數的棧空間中分配一個size字節大小的空間,當調用alloca()的函數返回或退出的時候,alloca()在棧中分配的空間被自動釋放。當alloca()函數執行成功時,它將返回一個指向所分配的棧空間起始地址的指針。

【示例21-7】 在棧上實現一個動態增長的變長數組。

#include<iostream>

//內存管理的庫函數頭文件

#include <malloc.h>

int main(int argc, char *argv[])

{

    //在棧上,內存爲10個int字節大小的內存存儲單元

    int stackArray[10]={1,2,3,4,5,6,7,8,9,10};

    int i;  //在棧上

    int j;  //在棧上

    int *p; //在棧上

    /*輸出原來存儲在棧上的數組中的10個元素 */

    std::cout << "the array before changed stack memeory: " << "/n";

    for(i=0;i<10;i++)

    {

        std::cout << stackArray[i] << "/t";  

    }

    std::cout <<  "/n";

    p = stackArray;  //指向stackArray數組在棧上的內存地址

    p = (int *) alloca(sizeof(int) * 12); //修改所指向的stackArray數組在棧上                                               的內存的大小,

                       //增加2個int字節大小的內存存儲單元。

                       //此時的statckArray在棧上分配的內存爲12個int字節大小的內存存                      儲單元

    stackArray[10] = 99;    /* 新增的第11個數組元素 */

    stackArray[11] = 100;   /* 新增的第12個數組元素 */

    /*輸出修改內存大小後的數組中的12個元素*/

    std::cout << "the array after changed stack memory: " << “/n”;

    for(j=0;j<12;j++)

    {

        std::cout << stackArray[j] << “/t”;

    }

    return 0;

}

程序執行時,顯示結果如下所示。

the array before chaged stack memory:

1 2 3 4 5 6 7 8 9 10

the array after chaged stack memory:

1 2 3 4 5 6 7 8 10 99 100

分析:示例中,首先用語句行int stackArray[10]在棧上申請了一個10個int字節大小內存存儲單元的棧數組。然後用C++庫函數alloca()對stackArray在棧上的內存大小進行了修改,成爲12個int字節大小的內存存儲單元。這樣增加了兩個int字節大小的內存單元,從而實現了對棧數組stackArray動態地增加了兩個int型的數組元素其值爲99和100。

%注意:用alloca()函數只能用於對棧內存的大小的修改。不能用於在堆上的操作。alloca()函數分配的內存不需要程序員來釋放,它是由系統在程序或調用它的函數運行結束後自動釋放。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章