malloc()
当它无法为内存请求提供服务时,返回一个无效的 NULL 指针。在大多数情况下,C 内存分配例程通过调用操作系统来管理可用内存的列表或堆,以在进行malloc()
调用并且列表或堆上没有块来满足请求时分配额外的内存块。
因此,第一种malloc()
失败情况是内存请求无法满足,因为(1)C 运行时的列表或堆上没有可用的内存块,以及(2)当 C 运行时内存管理请求更多内存时操作系统,请求被拒绝。
这是一篇关于指针分配策略的文章。
这篇论坛文章给出了一个由于内存碎片导致的 malloc 失败的例子。
可能失败的另一个原因malloc()
是内存管理数据结构已损坏,这可能是由于缓冲区溢出,其中分配的内存区域用于大于分配内存大小的对象。不同的版本可以使用不同的内存管理策略,并确定在调用malloc()
时提供多少内存。malloc()
例如,amalloc()
可能会准确地为您提供请求的字节数,或者它可能会为您提供比您要求的更多的信息,以适应内存边界内分配的块或使内存管理更容易。
使用现代操作系统和虚拟内存,除非您正在做一些非常大的内存驻留存储,否则内存不足是非常困难的。但是,正如用户 Yeow_Meng 在下面的评论中提到的那样,如果您正在做算术来确定要分配的大小并且结果是负数,那么您最终可能会请求大量内存,因为malloc()
要分配的内存量的参数是未签名。
在进行指针运算以确定某些数据需要多少空间时,您可能会遇到负大小的问题。这种错误对于对意外文本进行的文本解析很常见。例如,以下代码将导致非常大的malloc()
请求。
char pathText[64] = "./dir/prefix"; // a buffer of text with path using dot (.) for current dir
char *pFile = strrchr (pathText, '/'); // find last slash where the file name begins
char *pExt = strrchr (pathText, '.'); // looking for file extension
// at this point the programmer expected that
// - pFile points to the last slash in the path name
// - pExt point to the dot (.) in the file extension or NULL
// however with this data we instead have the following pointers because rather than
// an absolute path, it is a relative path
// - pFile points to the last slash in the path name
// - pExt point to the first dot (.) in the path name as there is no file extension
// the result is that rather than a non-NULL pExt value being larger than pFile,
// it is instead smaller for this specific data.
char *pNameNoExt;
if (pExt) { // this really should be if (pExt && pFile < pExt) {
// extension specified so allocate space just for the name, no extension
// allocate space for just the file name without the extension
// since pExt is less than pFile, we get a negative value which then becomes
// a really huge unsigned value.
pNameNoExt = malloc ((pExt - pFile + 1) * sizeof(char));
} else {
pNameNoExt = malloc ((strlen(pFile) + 1) * sizeof(char));
}
良好的运行时内存管理将尝试合并已释放的内存块,以便许多较小的块在释放时组合成较大的块。这种内存块的组合减少了无法使用 C 内存管理运行时管理的列表或内存堆上已经可用的内存来服务内存请求的机会。
您可以重用已分配内存的越多,依赖malloc()
的free()
越少越好。如果你不做,malloc()
那么它很难失败。
您可以将许多小型调用更改malloc()
为更少的大型调用,从而malloc()
减少内存碎片和扩展内存列表或堆大小的机会,这些小块无法组合,因为它们不是下一个对彼此。
您可以malloc()
同时free()
处理的连续块越多,内存管理运行时就越有可能合并块。
没有规则说您必须对malloc()
对象的特定大小执行 a,提供的 size 参数malloc()
可以大于您为其分配内存的对象所需的大小。因此,您可能希望对调用使用某种规则,malloc ()
以便通过四舍五入到某个标准内存量来分配标准大小的块。因此,您可以使用 ((size / 16) + 1) * 16 或更可能 ((size >> 4) + 1) << 4 之类的公式分配 16 个字节的块。许多脚本语言使用类似的东西以便增加重复调用的机会,malloc()
并free()
能够将请求与列表或内存堆上的空闲块匹配。
这是一个尝试减少分配和释放的块数量的简单示例。假设我们有一个可变大小的内存块的链表。所以链表中节点的结构看起来像:
typedef struct __MyNodeStruct {
struct __MyNodeStruct *pNext;
unsigned char *pMegaBuffer;
} MyNodeStruct;
可能有两种方法可以为特定缓冲区及其节点分配此内存。第一个是节点的标准分配,然后是缓冲区的分配,如下所示。
MyNodeStruct *pNewNode = malloc(sizeof(MyNodeStruct));
if (pNewNode)
pNewNode->pMegaBuffer = malloc(15000);
然而,另一种方法是执行类似以下的操作,它使用带有指针算法的单个内存分配,以便单个malloc()
提供两个内存区域。
MyNodeStruct *pNewNode = malloc(sizeof(myNodeStruct) + 15000);
if (pNewNode)
pNewNode->pMegaBuffer = ((unsigned char *)pNewNode) + sizeof(myNodeStruct);
但是,如果您使用这种单一分配方法,则需要确保您在使用指针时保持一致pMegaBuffer
,您不会意外地free()
对其进行操作。如果您必须用更大的缓冲区更改缓冲区,则需要释放节点并重新分配缓冲区和节点。所以程序员有更多的工作。