首页 文章

对C的动态内存分配的确切含义有点困惑

提问于
浏览
1

我听说过有关动态的确切含义的冲突事件,以及自动内存分配 . 我听说堆栈被称为自动内存分配和动态内存分配 . 我可以看到两者都是在程序执行之前确定堆栈内存块大小,因此它的最大大小在运行时不会增长 . 但是,在程序执行期间,当函数数据被推入和弹出堆栈时,堆栈会不断增长和缩小 .

那么在这个意义上是不是这种动态内存分配?

如果它是不是混淆只是将堆称为动态?

有人可以帮我澄清一下吗?

编辑:在撰写本文时,我似乎混淆了一些我不知道的概念 . 堆栈和堆内存管理的低级别结构与C中相同内容的高级概念之间存在差异 . 有关此问题的澄清,请参阅下面我接受的答案 .

2 回答

  • 0

    我会尽可能地清除混乱 . 首先,学习将低级内存模型概念(堆栈,堆)与c级内存概念分开 . 在C世界中, stackheap 并不意味着在低级模型中远程类似堆栈或堆 .

    低级内存模型

    首先,我们来谈谈低级内存模型 . 传统上,内存在“堆栈”和“堆”内存之间分配,我将在下面介绍 .

    堆栈

    堆栈由所谓的“堆栈指针”CPU寄存器管理 - 它总是指示堆栈的顶部,并从高级存储器地址连续到低级存储器地址 . 由于堆栈的顶部始终由寄存器指向,因此不需要任何与堆栈相关的实际内存管理 - 当您需要更多内存时,您只需减少存储在指针中的值 - 这就是您的内存现在,它是被认为是为你分配的 . 当你不再需要内存时,你会增加值 - 现在内存是“免费的” . 显然,这种方法的问题在于它不可持续 - 你不能在块内释放(或分配)内存 . 因此,如果为3个对象分配内存,A,B,C并且您不再需要对象B,则不需要您可以说B占用的内存可以自由使用 - 单个堆栈指针根本没有能力这样做 .

    这限制了堆栈内存对“近距离”,短期对象的使用 - 当您知道不需要有选择地释放与此范围内分配的对象关联的任何内存时,可以简单地释放所有内存很快他们这使得堆栈存储器成为函数中定义的变量的理想存储 - 当函数退出时,它们全部被释放 . 更好的是编译器可以自动为您执行此操作 - 您不必明确告诉编译器何时为每个变量释放内存 - 一旦代码执行离开它的范围,它将自动释放 .

    值得注意的是,堆栈分配和释放是不可能的 - 它们只需要一个寄存器算术运算 .

    但是,正如我之前所说,堆栈有局限性 . 堆内存在这里是为了克服这些 - 并将在下面进行描述 .

    与堆栈(仅由简单寄存器管理)不同,堆内存由复杂的结构和逻辑支持 . 您可以从堆中请求内存,并且可以将内存返回到堆,并且可以针对每个对象单独执行 . 所以,回到我原来的例子,当你为对象A,B和C(所有相同的大小)请求内存,并且不再需要对象B时,你可以为B返回内存并仍然保留A和C.如果你需要创建另一个与之前大小相同的对象D并为它寻求内存,堆可以给你从B返回的内存 . 虽然不能保证(堆算法非常复杂),这是一个很好的简化 .

    与堆栈内存不同,管理堆内存有成本,实际上相对较高(特别是在多线程环境中) . 这就是为什么不应该使用堆内存,如果一个人可以帮助它,但这是一个很大的话题,我现在不打算讨论 .

    堆内存的一个非常重要的属性是它必须由用户显式管理 . 您需要时需要记忆它,当你不再需要它时,把它还给它,永远不要使用你已经给出的记忆 . 不遵守这些规则会导致程序泄漏内存 - 即消耗内存而不返回内存,这会导致程序最终耗尽内存 - 以防您不回忆内存;或导致程序行为不正确(如果您在请求之前或在回馈之后使用内存),因为您将访问不属于您的内存 .

    C / C内存模型

    无论好坏,C / C都会使程序员免受那些低级内存概念的影响 . 相反,该语言指定每个变量都存在于某种类型的存储中,并且它的生命周期由存储类型定义 . 存储有3种类型,如下所述 .

    自动存储

    这个存储由编译器“自动”管理(因此名称),并且不需要程序员对它做任何事情 . 自动变量的一个例子是在函数体内定义的一个:

    void foo() {
       int a;
    }
    

    a 这里是自动的 . 你不需要担心为它分配内存或者在不再需要它时清理它,并且编译器保证你在输入函数foo()时它会在那里,当你退出foo()时它将不再存在 . 虽然它在堆栈上被分配,但绝对不能保证它 - 它也可以放在寄存器中 . 寄存器比任何内存都快得多,因此编译器会尽可能地使用它们 .

    静态存储

    存放在静态存储中的变量会一直存在,直到程序退出 . 同样,开发人员不需要担心他们的生命周期,或者清理内存 - 程序退出后内存将被清除,而不是之前 . 静态持续时间变量的示例是在任何函数(全局变量)之外定义的变量,函数的静态局部变量以及类的静态成员 . 在var1下面的代码中,var2和var3都是静态存储中的变量:

    代码(带一些内联注释):

    int var1;
    
    void foo() {
        static int var2;
    }
    
    class A {
       static int var3;
    }
    

    动态存储

    动态存储变量由开发人员控制 . 当您需要它们时,您需要内存(通常在C中使用 malloc 或在C中使用 new )并且必须在不再需要时将其返回(C中为 free ,C中为 delete ) . 作为开发人员,您应该全神贯注地分配,使用和删除它们,并确保序列永远不会被破坏 . 没有观察序列是造成新闻的所有重大程序错误的一个主要原因:) . 幸运的是,C为您提供了简化此任务的特殊功能和类,但如果您使用C开发,则可以自行完成 . 在下面的示例中,动态分配var4点的内存 .

    码:

    void foo() {
       int* var4;
       // Here is the major source of confusion. var4 itself is **automatic**
       // you do not need to allocate or free var4 memory, so you can use it
       // like this:
       var4 = NULL; // Not an error!!!
       // However, you can't use the memory var4 points to yet!
       // Following line would cause incorrect behavior of the program:
       // *var4 = 42; // NEVER EVER!!!
       // Instead, you need to allocate the memory first (let's assume, we are in C++
       var4 = new int();
       // Now the memory was allocated, we can use it
       *var4 = 42; // Correct!
       // we no longer need this memory, so let's free it:
       delete var4;
       // This did not change var4 itself (unless there is a special case)
       // so technically, it still points to the memory which was former 
       // belonging to you. But the memory is no longer yours!!!
       // you can't read or write it!
       // Following code is bad-bad-bad:
       // int x = *var4; // NEVER EVER! 
    }
    

    正如您所见,使用动态内存时会出现最谨慎和警告的迹象 . 这就是为什么在C中有一些特殊的工具可以使这更容易,并且没有人期望编写我上面写的代码 . 但是,我的帖子已经很长了,所以在C中适当的内存管理将留给另一个场合:)

  • 9

    根据Wikipedia

    C动态内存分配是指执行手动内存管理 .

    在这种意义上,堆栈不是动态的,因为堆栈变量的大小必须在编译时知道:

    C编程语言静态,自动或动态管理内存 . 静态持续时间变量在主存储器中分配,通常与程序的可执行代码一起分配,并在程序的生命周期内持续存在;自动持续时间变量在堆栈上分配,随着函数的调用和返回而变化 . 对于静态持续时间和自动持续时间变量,分配的大小必须是编译时常量 . [..]分配内存的生命周期也会引起关注 . [..]使用动态内存分配可以避免这些限制 .

    实际上,即使在堆栈上也可以通过多种方式动态分配内存:

    • 使用 alloca() 函数;

    • 使用自C99标准以来可用于C的可变长度数组或作为GCC扩展;

    • 使用C实验 dynarray 容器 .

    然而,堆栈存储器的这些使用并不典型,并且可能更重要的是,这样做仍然不能提供关于对象寿命的额外灵活性 .

    你的第二个困惑是关于堆栈的增长 . 是的,确定堆栈的最大大小静态 . 但是,最大大小远远大于正常程序所需的大小(Linux上默认为8 MB),甚至可以通过使用操作系统API(Linux上的 setrlimit )在运行时更改该数字 . 在程序执行期间,堆栈的实际大小会动态增长和缩小,直至达到此限制 .

相关问题