如何避免使用malloc?

时间:2013-01-22 12:59:23

标签: c malloc

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

P.S。我读过的一些帖子引用了Quake 3源代码以及它如何避免使用malloc,所以如果有人知道这一点,那么知道在那里做了什么会很有趣,因为至少知道我想避免挖掘地震代码漫无目的。 (因为如果他们避免使用malloc搜索malloc,我认为不会产生太多结果,而且代码库很可能不像单个示例那么简单)

6 个答案:

答案 0 :(得分:12)

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

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

您可以在实际情况下使用它,在这种情况下,您将事件发送到队列以由另一个线程处理。事件对象可能是小的结构,你真的需要避免每秒拨打malloc数千个。

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

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


不要害怕这些概念。游泳池实际上不一定非常复杂。考虑一下:

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

答案 1 :(得分:3)

在本地范围内需要小型,动态大小的数组的场景中,有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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

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

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

例如,分配器假设分配的对象具有固定大小或假设其生命周期相对较短是更有利的。通用分配器不能做出这样的假设,因此不能在这种情况下以最佳方式执行。

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

它也可以改变分配速度 - 自定义分配器可能能够更快地找到空槽。

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

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

  • 这些对象可以立即处理,而不需要单独调用的开销吗? (记忆池)

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

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

答案 4 :(得分:2)

有几个理由可以避免malloc - 我脑海中最大的一个原因是“没有malloc,没有自由”来解释Bob Marley ...所以,没有内存泄漏“忘记”调用{{ 1}}。

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

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

另一部分当然是与局部变量相比,对NULL的调用可能相当昂贵。当你在“热路径”中点击malloc / free调用时尤其如此 - 这些部分代码经常被调用。在小内存部分使用malloc时也会产生内存开销 - Visual Studio中过去经验的开销大约是32个字节的“标题”并四舍五入到16或32个字节的边界 - 因此实际需要1个字节的分配高达64个字节。分配17个字节也需要64个字节...

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

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

答案 5 :(得分:2)

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

您可以在malloc()或您的操作系统提供的任何内容上实现自定义内存分配器,分配一大块内存,然后在不调用标准malloc()函数的情况下进行分割。优点:快。缺点:实施权利并不容易。

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

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

作为一个工作中型项目的例子,它避免malloc()我可以提供我的宠物项目,Smaller C compiler。它静态地分配了许多数组,并且还在递归函数中分配了小的局部变量。请注意,代码尚未被美化,如果您对编程,C或编译器还不熟悉,那么这些代码并不容易理解。