栈内存管理

来源:互联网 发布:javascript编程视频 编辑:程序博客网 时间:2024/06/05 01:01

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()函数分配的内存不需要程序员来释放,它是由系统在程序或调用它的函数运行结束后自动释放。

原创粉丝点击