【问题标题】:Ways to avoid using malloc? [closed]避免使用malloc的方法? [关闭]
【发布时间】:2013-01-22 12:59:23
【问题描述】:

鉴于最近 C 得到的所有噪音,我读到有一些方法可以最大限度地减少 C 中 malloc 的使用,这是一个非常好的做法。我怎么也不知道这种做法何时、如何或什么是好的。所以我的问题是,也许一些有经验的 C 程序员可以举一些例子,其中一个人可以(或应该)在没有 malloc 的情况下编写一些东西,但是对于新手 C 程序员来说,什么是真正不明显的方式(因此说新手只会使用 malloc) ?也许你有一些将 malloc 分解成其他东西的经验。

附:我读过的一些帖子引用了 Quake 3 源代码以及它如何避免使用 malloc,所以如果有人知道这一点,知道那里做了什么会很有趣,因为至少知道我想避免深入研究 quakes 代码漫无目的地。 (因为如果他们避免使用 malloc 搜索 malloc 不会给出太多结果,我想,代码库也很可能不像单个示例那样简单)

【问题讨论】:

  • With all recent noise C gets... -- 需要引用:)
  • 普通 C 程序员在内存管理方面可以做的大部分优化已经在主流 malloc() 实现中实现。引用 Knuth 的话:“过早的优化是万恶之源。”除非您知道它是给定代码中的瓶颈,否则请使用 malloc()。
  • 如果没有 Knuth 的话,我们可以在 SO 上度过一整天吗?
  • @Jon 很好,如果一个人不研究高级主题,一个人就不会成为高级:)
  • @morphles:当然。我只是说新手可能首先需要更多的经验才能有效地消化信息。

标签: c malloc


【解决方案1】:

我不知道完全避免malloc,但你当然可以减少它。

基本概念是内存池。这是您分配的一个大缓冲区,您可以将其用于许多对象,而不是请求大量的小分配。

您可能会在实际情况中使用它,您将事件发送到队列中以由另一个线程处理。事件对象可能是小型结构,您确实需要避免每秒对malloc 进行数千次调用。

答案当然是从池中提取这些事件对象。如果需要,您甚至可以使用部分池缓冲区来形成一个列表,以便您可以快速索引已返回池的内存。这些通常被称为 free-lists

您必须小心内存对齐,因为数据未对齐会严重影响性能。但是你可以用一点数学来处理所有这些。


不要对这些概念感到害怕。游泳池实际上不必那么复杂。考虑一下:

int ** matrix = malloc( rows * sizeof(int*) );
for( int i = 0; i < rows; i++ ) {
    matrix[i] = malloc( cols * sizeof(int) );
}

我经常看到这种情况,这是我最讨厌的。你为什么要这样做,什么时候可以这样做:

int ** matrix = malloc( rows * sizeof(int*) );
matrix[0] = malloc( rows * cols * sizeof(int) );
for( int i = 1; i < rows; i++ ) {
    matrix[i] = matrix[i-1] + cols;
}

当然,这会减少到这一点(但要注意第一行中潜在的对齐问题 - 为了清楚起见,我在这里忽略了它)

int ** matrix = malloc( rows * sizeof(int*) + rows * cols * sizeof(int) );
matrix[0] = (int*)matrix + rows;
for( int i = 1; i < rows; i++ ) {
    matrix[i] = matrix[i-1] + cols;
}

关于最后一个示例的很酷的一点是删除矩阵是多么容易 =)

free( matrix );

哦,对矩阵进行归零同样简单...

memset( matrix[0], 0, rows * cols * sizeof(int) );

【讨论】:

    【解决方案2】:

    在您需要在本地范围内的小型动态大小数组的场景中,alloca() 从堆栈中分配并且不需要您显式释放内存(它在函数返回时被释放),并且有variable length arrays (VLA)

    void meh(int s) {
        float *foo = alloca(s * sizeof(float));
        float frob[s];
    } // note: foo and frob are freed upon returning
    

    【讨论】:

    • 前支持者:你为什么撤销你的支持?否决票:否决票的原因消失了吗?
    • 虽然我两者都不是......但我敢打赌,投反对票可能是由于帖子(听起来像是)它要求使用通用 C 标准替换 malloc ,其中alloca 取决于机器和编译器。无论如何,我认为它值得为一个独特的想法 +1。
    • 我不知道alloca,所以它很有用。谢谢。
    • @Mike:的确,这是个脑残,因为我的意思是我现在已经更正了。
    • C99 中的灵活数组成员(或称为“struct hack”的 C90 未定义行为)依赖于对 malloc 的显式调用,这与 VLA 不同。
    【解决方案3】:

    如果您事先知道数组、列表、堆栈、树以及程序所需的任何数据结构的所有大小,则可以通过定义元素数量恒定的数组来静态分配所需的内存。优点:没有内存管理,没有内存碎片,速度快。缺点:使用有限,浪费内存。

    您可以在malloc() 或您的操作系统提供的任何东西之上实现自定义内存分配器,一次分配一大块内存,然后将其分割,而无需调用标准malloc() 函数。优点:快。缺点:实施起来并不容易。

    避免malloc() 的另一种(也是相当反常的)方法是将大部分数据存储在文件中而不是内存中。优点:几乎没有。

    如果您确定程序的堆栈足够大,您还可以使用局部变量和深度函数调用(或显式递归)为移动中的数据分配空间。优点:没有内存管理,简单,快速。缺点:使用有限。

    作为一个避免使用malloc() 的中型项目的示例,我可以提供我的宠物项目Smaller C compiler。它静态分配了许多数组,并且还在递归函数中分配了小的局部变量。请注意,代码还没有被美化,如果您是编程、C 或编译器的新手,这不是什么小东西或容易理解的东西。

    【讨论】:

    • 这是迄今为止最好的答案。下一个最佳答案是“不要使用 malloc”,然后是“使用 C#/java”。人类无时无刻不在犯错,为什么不使用一种能够消除这种错误能力的语言呢??
    • @rolls 因为一种尺寸并不适合所有人。
    【解决方案4】:

    在某些特定情况下不使用malloc 的主要原因可能是它采用了一种通用的、一刀切的内存分配方法。

    其他方法,例如内存池和slab分配,在有众所周知的分配需求的情况下可能会带来好处。

    例如,对于分配器来说,假设分配的对象将具有固定大小,或者假设它们的生命周期相对较短,则更为有利。通用分配器无法做出此类假设,因此无法在此类场景中发挥最佳性能。

    潜在的好处可能包括减少内存占用,因为专用分配器具有更精简的簿记。通用分配器很可能为每个分配的对象保存大量元数据,而提前“知道”对象大小的分配器可能会从元数据中省略它。

    它还可以在分配速度方面产生影响 - 自定义分配器可能能够更快地找到空槽。

    这里都是亲戚说的,但是在选择自定义分配方案之前你应该问的问题是:

    • 您是否需要分配和释放大量具有相同大小的对象? (平板分配)

    • 是否可以一次处理这些对象而无需单独调用的开销? (内存池)

    • 是否有单独分配的对象的逻辑分组? (缓存感知分配)

    最重要的是,您必须仔细检查程序的分配需求和模式,然后决定自定义分配方案是否有益。

    【讨论】:

      【解决方案5】:

      避免使用malloc 的原因有很多——在我看来,最重要的一个原因是用 Bob Marley 的话来说“没有 malloc,没有空闲”......因此,“忘记”调用 free 不会导致内存泄漏。

      当然,在动态分配内存时,您应该始终检查NULL。避免这种情况会减少代码量和代码的复杂性。

      不幸的是,用完堆栈或全局变量大小的替代方案通常更糟糕,因为它要么立即崩溃,而对用户没有任何有意义的错误消息(stackoverflow),要么全局变量中的缓冲区溢出 - 检查全局变量中的边界会避免这种情况,但如果你检测到它,你会怎么做?没有太多选择。

      当然,与局部变量相比,对malloc 的调用可能会非常昂贵。当您在“热路径”中点击 malloc/free 调用时尤其如此 - 经常调用的部分代码。在小内存部分上使用malloc 也会产生内存开销——过去在 Visual Studio 中的开销约为 32 字节的“标题”并四舍五入为 16 或 32 字节边界——因此 1 字节的分配实际上占用了 64字节。分配 17 个字节也会占用 64 个字节...

      当然,就像所有工程/软件设计一样,它不是“您不得使用 malloc”,而是“如果有简单/合适的替代方案,请避免使用 malloc”。使用比它们需要的数倍大的所有全局变量是错误的,只是为了避免 malloc - 但是为图形绘制循环的每一帧或每个对象调用 malloc/free 也是同样错误的。

      我没有查看 Quake 的代码,但我在 3DMark 2000 中编写了一些代码 [我认为我的名字仍在产品的学分中]。这是用 C++ 编写的,但它避免在渲染代码中使用 new/delete。这一切都是在框架的设置/拆卸中完成的,很少有例外。

      【讨论】:

        【解决方案6】:

        分配更大的内存块通常更快,所以我的建议是分配一个大块,然后从中创建一个内存池。实现您自己的函数以将内存“释放”回池中并从中分配内存。

        【讨论】:

        • 内存池的一个关键特性不仅是能够像使用 malloc/free 那样以对称方式释放分配的对象,而且还能够解除分配一次释放整个池,消除了发出单独调用以释放每个对象的开销。
        • @BlagovestBuyukliev 我想我已经说得很清楚了,虽然我只是说分配,但你当然是对的。
        猜你喜欢
        • 2023-04-01
        • 1970-01-01
        • 2014-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-11
        • 1970-01-01
        • 2017-03-26
        相关资源
        最近更新 更多