如何避免使用malloc?

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

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

我不知道完全避免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) ); 

在本地范围内需要小型,动态大小的数组的场景中,有一个alloca()从堆栈中分配,不需要你显式释放内存(它在函数返回时被释放),并且有变量长度数组(VLA) :

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

避免使用malloc有几个原因 – 我脑海中最大的一个原因就是“没有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。 这一切都是在框架的设置/拆卸中完成的,只有极少数例外。

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

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

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

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

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

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

在具有众所周知的分配需求的情况下,诸如存储器池和板块分配的其他方法可以提供益处。

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

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

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

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

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

  • 这些物品可以同时处理,而不需要单独呼叫的开销吗? (记忆池)

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

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

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