【问题标题】:Differences between dynamic memory and "ordinary" memory动态内存和“普通”内存的区别
【发布时间】:2010-11-04 12:03:32
【问题描述】:

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

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

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

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

...调用它,函数分配的内存会在它的存在范围结束后立即释放吗?那么动态内存呢?

【问题讨论】:

  • 我认为您正在寻找的术语是堆栈和堆。当您使用newmalloc() 时,您从堆中分配内存。当您在函数中分配对象时(如在int x = 2 中),它会进入堆栈。
  • 您认为使用“new”创建对象需要更多内存的假设是正确的,因为访问对象需要指针。但是,由于动态内存管理与静态 MM 的用途不同,因此比较没有意义。嘿,它只有 4 个字节! ;-)
  • @Johannes:如果您使用的是 64 位操作系统,我假设您有足够的内存,8 字节也无所谓;-)
  • 好吧,4 个字节和 8 个字节都不是很重要。单个指针真的是我不在乎的东西。其中有几百万......好吧,然后它开始变得有趣:-)。但是指针不一定总是 4 个字节,这就是我想说的 :-)

标签: c++ memory memory-management


【解决方案1】:

注意:这个答案方式太长了。有时间我会删减的。同时,如果您能想到有用的修改,请发表评论。


要回答您的问题,我们首先需要定义两个内存区域,称为stackheap

堆栈

将堆栈想象成一堆盒子。每个框代表一个函数的执行。一开始,当调用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;
}

现在,地板上有一个盒子(用于main),上面有argcargvab。在该框的顶部,您还有另一个框(用于do_stuff),带有abc

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

  1. 您可能知道,ab 是按值传递的。这就是为什么do_stuff 的框中有这些变量的副本

  2. 请注意,对于这些变量,您不必为 freedelete 或任何东西。当您的函数返回时,该函数的框被销毁。

框溢出

    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);
    }

这里,地板上有一个盒子(main,和以前一样)。然后,您有一个带有ab 的框(用于do_stuff)。然后,你有另一个盒子(do_stuff 调用自己),同样是 ab。然后另一个。很快,你就有了一个堆栈溢出

堆栈摘要

将堆栈想象成一堆盒子。每个框代表一个正在执行的函数,并且该框包含该函数中定义的局部变量。当函数返回时,那个盒子就被销毁了。

更多技术资料

  • 每个“盒子”被正式称为一个堆栈帧
  • 是否注意到您的变量是如何具有“随机”默认值的?当一个旧的堆栈帧被“销毁”时,它就不再相关了。它不会被归零或类似的东西。下次堆栈帧使用该部分内存时,您会在局部变量中看到旧堆栈帧的位。

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

将堆想象成一片无尽的绿色记忆草地。当您调用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[]它。

【讨论】:

    【解决方案2】:

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

    在函数中分配的内存驻留在函数被放置在堆栈上的函数内部。

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

    【讨论】:

      【解决方案3】:

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

      关于函数中内存使用的问题,在您的示例中,局部变量的内存将在函数结束时释放。但是,如果内存是用new动态分配的,它不会被自动释放,你需要负责显式使用delete来释放内存。

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

      【讨论】:

        【解决方案4】:

        使用 new 运算符分配的内存从称为“堆”的内存部分获取,而变量的静态分配使用与过程/函数调用(“堆栈”)共享的内存部分。

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

        【讨论】:

          【解决方案5】:

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

          C++ 根本不支持动态内存。

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

          你需要一个额外的指针,因为你需要访问你的新内存。一些内存(无论是否动态)首先是编程语言无法处理的。您需要有权访问它。这是由变量完成的。但是像 C++ 这样的语言中的变量存储在“普通”内存中。所以你需要有“指针”——指针是一种间接形式,它表示“不,我不是你要搜索的值,但我指向它”。指针是 C++ 中访问动态内存的唯一可能性。

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

          动态内存和指针是 C++ 中问题的最大根源——但它也是一个非常强大的概念——如果你做对了,你可以比普通内存做更多的事情。

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-01-13
            • 1970-01-01
            • 1970-01-01
            • 2011-11-23
            • 1970-01-01
            • 2014-07-30
            • 1970-01-01
            相关资源
            最近更新 更多