动态内存和“普通”内存之间的差异

时间:2009-06-20 07:20:57

标签: c++ memory memory-management

使用new运算符分配的内存与通过简单变量声明分配的内存(例如int var)之间有哪些技术差异? c ++是否有任何形式的自动内存管理?

特别是,我有几个问题。首先,由于使用动态内存,您必须声明一个指针来存储您使用的实际内存的地址,动态内存是否使用 more 内存?除非你声明一个数组,否则我不明白为什么指针是必要的。

其次,如果我要做一个像这样的简单函数:

int myfunc() { int x = 2; int y = 3; return x+y; }

...并且调用它,一旦它的存在范围结束,函数分配的内存是否会被释放?动态记忆怎么样?

5 个答案:

答案 0 :(得分:20)

注意:这个答案方式太长了。我会在某个时候削减它。同时,如果您能想到有用的编辑,请发表评论。


要回答您的问题,我们首先需要定义两个记忆区域,分别称为堆栈

堆栈

想象一下堆栈是一堆盒子。每个框表示一个函数的执行。一开始,当调用main时,地板上有一个盒子。您定义的任何局部变量都在该框中。

一个简单的例子

int main(int argc, char * argv[])
{
    int a = 3;
    int b = 4;
    return a + b;
}

在这种情况下,你在地板上有一个框,变量argc(整数),argv(指向char数组的指针),a(整数) ,和b(整数)。

多个方框

int main(int argc, char * argv[])
{
    int a = 3;
    int b = 4;
    return do_stuff(a, b);
}

int do_stuff(int a, int b)
{
    int c = a + b;
    c++;
    return c;
}

现在,您在mainargcargva的地板上(b)有一个方框。在该框的顶部,您有另一个包含do_stuff abc的框(a)。

这个例子说明了两个有趣的效果。

  1. 您可能知道,bdo_stuff是按值传递的。这就是free框中存在副本的原因。

  2. 请注意,这些变量不需要delete int main(int argc, char * argv[]) { int a = 3; int b = 4; return do_stuff(a, b); } int do_stuff(int a, int b) { return do_stuff(a, b); } 或其他任何内容。当您的函数返回时,该函数的框将被销毁。

  3. 框溢出

    main

    在这里,你在地板上有一个方框(对于do_stuff,和以前一样)。然后,您有一个包含ab的{​​{1}}}框。然后,您再次使用do_stuffa来设置另一个框(b调用自身)。然后另一个。很快,你有一个堆栈溢出

    堆栈摘要

    将堆栈视为一堆盒子。每个框表示一个执行的函数,该框包含该函数中定义的局部变量。当函数返回时,该框被销毁。

    更多技术性的东西

    • 每个“盒子”官方称为堆栈框架
    • 您是否注意到您的变量具有“随机”默认值?当旧的堆栈帧被“破坏”时,它就会停止相关。它不会被归零或类似的东西。下一次堆栈帧使用该部分内存时,您会在局部变量中看到旧堆栈帧的位。

    这是动态内存分配发挥作用的地方。

    想象一下堆是无尽的绿色记忆草甸。当您调用mallocnew时,会在堆中分配一块内存。您将获得一个指针来访问此内存块。

    int main(int argc, char * argv[])
    {
        int * a = new int;
        return *a;
    }
    

    这里,在堆上分配一个新整数的内存。你得到一个名为a的指针指向那个记忆。

    • a是一个局部变量,因此它位于main的“框”中

    动态内存分配的基本原理

    当然,使用动态分配的内存似乎浪费了几个字节来指示。但是,如果没有动态内存分配,有些事情你不能(轻松)做到。

    返回数组

    int main(int argc, char * argv[])
    {
        int * intarray = create_array();
        return intarray[0];
    }
    
    int * create_array()
    {
        int intarray[5];
        intarray[0] = 0;
        return intarray;
    }
    

    这里发生了什么?你在create_array中“返回一个数组”。实际上,您返回一个指针,该指针只指向包含该数组的create_array“框”部分。 create_array返回时会发生什么?它的盒子被破坏了,你可以期待你的阵列随时变得腐败。

    相反,请使用动态分配的内存。

    int main(int argc, char * argv[])
    {
        int * intarray = create_array();
        int return_value = intarray[0];
        delete[] intarray;
        return return_value;
    }
    
    int * create_array()
    {
        int * intarray = new int[5];
        intarray[0] = 0;
        return intarray;
    }
    

    因为函数返回不会修改堆,所以您的宝贵intarray没有受到损害。完成后请记住delete[]

答案 1 :(得分:4)

动态内存存在于堆上,而不是堆栈。动态内存的生命周期是从分配时到释放时。对于局部变量,它们的生命周期仅限于它们所定义的函数/块。

关于函数中内存使用情况的问题,在您的示例中,函数末尾将释放本地变量的内存。但是,如果内存是使用new动态分配的,则不会自动处理,您将负责明确使用delete释放内存。

关于自动内存管理,C ++标准库为此提供了auto_ptr。

答案 2 :(得分:4)

“new”分配的内存最终会堆在堆上。

函数中分配的内存位于函数放置在堆栈中的函数内。

在此处阅读堆栈与堆分配:http://www-ee.eng.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.8.html

答案 3 :(得分:2)

使用new运算符分配的内存是从名为“heap”的内存部分获取的,而变量的静态分配是使用与procedure / function-calls共享的内存部分(“stack”)。

您只需要担心自己使用new创建的动态内存分配,编译时已知的变量(在源中定义)会在其作用域结束时自动释放(函数/过程结束,块) ,...)。

答案 4 :(得分:1)

“动态”和“普通”记忆之间的巨大差异在问题本身中得到了很好的反映。

C ++完全不支持动态内存。

当您使用动态内存时,您自己应对此负全部责任。你必须分配它。当你忘记这样做并尝试访问它时扔了你的指针,你会有很多负面的惊喜。此外,你必须释放记忆 - 当你以任何方式忘记它时,你会有更多的惊喜。这些错误属于C / C ++程序中最难发现的错误。

你需要一个额外的指针,因为不知何故你需要访问你的新内存。一些内存(如果是动态的或非动态的)首先是编程语言无法处理的内容。您需要访问它。这是由变量完成的。但是像C ++这样的语言中的变量存储在“普通”内存中。所以你需要有“指针” - 指针是间接的一种形式,它说“不,我不是你要寻找的价值,但我指向它”。指针是C ++访问动态内存的唯一可能性。

相比之下,“普通”内存可以直接访问,分配和释放由语言本身自动完成。

动态内存和指针是C ++问题的最大来源 - 但它也是一个非常强大的概念 - 当你做得对时,你可以用普通的内存做更多的事情。

这也是原因,很多库都有处理动态内存的函数或整个模块。 auto_ptr-example也在并行答案中提到,它试图解决问题,动态内存应该在方法结束时可靠地释放。

通常,只有在您真正需要的情况下才会使用动态内存。你不会使用它来拥有一个整数变量,而是拥有数组或在内存中构建更大的数据结构。