C语言中的内存分配

5
以下是非常简单版本的malloc()函数,看起来给我分配了一些空间,但除了没有free()函数和我没有检查是否超出了分配的空间外,我如何检查代码是否正确?
有没有任何明显的错误会让"C"专家批评?
#include <stdio.h>
#include <unistd.h>

#define MAX_MEMORY 1024 * 1024 * 2 /* 2MB of memory */

void *stack = NULL; /* pointer to available stack */
void * memoryAlloc(size) {
    if (stack == NULL)
        stack = sbrk(MAX_MEMORY); /* give us system memory */

    void *pointer;
    pointer = (void *)stack + size; /* we always have space :) */
    stack += size; /* move in stack forward as space allocated */
    return pointer;
}

我不会把内存的领域称为“堆栈”。你目前的实现像一个堆栈,是的,但如果你打算让它像malloc一样工作,并且有free函数,那么它就是一个堆。 - Thomas Padron-McCarthy
非常感谢大家的评论! - Radek
这个分配器是所谓的“池分配器”的开端 - 分配发生在上述情况下,然后当使用池的工作单元完成时,整个块一次性释放回系统。它有助于处理由于必须单独管理每个小分配而导致的泄漏。Apache使用池 - HTTP请求进来时,为请求设置一个池,在请求完成时释放池。其他处理请求的内容无需担心释放动态分配的对象。 - Michael Burr
3个回答

11

除了Ned Batchelder提出的基本问题外,一个更加微妙的问题是,分配器必须返回一个对于正在分配的任何对象而言适当对齐的地址。在一些平台上(如x86),这可能只涉及性能问题,但在许多平台上,这是个致命问题。

我还需要执行(char*)转换来执行stack指针算术操作(你不能在void*类型上进行指针算术运算)。

MAX_MEMORY宏表达式中应该加上括号。如果没有括号,我认为不会有任何优先级问题,因为所有高优先级运算符都不是正确的语法。使用宏时,最好总是安全第一。(至少有一个例外情况,即[]运算符仅绑定到2而不是整个MAX_MEMORY表达式,但即使这是符合语法的,也很少见到MAX_MEMORY[arrayname]这种奇怪的情况。)

实际上,我会将其制作成枚举。

你可以通过返回一个对于系统上的任何基本数据类型都适当对齐的内存块(可能是8字节对齐)来保持分配器简单:

/* Note: the following is untested                   */
/*       it includes changes suggested by Batchelder */

#include <stdio.h>
#include <unistd.h>

enum {
    kMaxMemory = 1024 * 1024 * 2, /* 2MB of memory */
    kAlignment = 8
};

void *stack = NULL; /* pointer to available stack */
void * memoryAlloc( size_t size) {
    void *pointer;

    size = (size + kAlignment - 1) & ~(kAlignment - 1);   /* round size up so allocations stay aligned */

    if (stack == NULL)
    stack = sbrk(kMaxMemory); /* give us system memory */

    pointer = stack; /* we always have space :) */
    stack = (char*) stack + size;   /* move in stack forward as space allocated */
    return pointer;
}

6

存在一些问题:

  1. 你在函数中间声明了指针pointer,这在C语言中是不允许的。

  2. 你将指针设置为stack+size,但你想要它只是stack。否则,你返回的是一个指向你分配的内存块末尾的指针。结果,如果你的调用者在该指针处使用了所有size字节,他将与另一个内存块重叠。如果你在不同的时间获得不同大小的内存块,那么两个调用者将尝试使用相同的内存字节。

  3. 当你执行stack += size时,你增加的不是size字节,而是size个void*,几乎总是更大。


3
自C99版本起,允许在函数中间进行变量声明。 - Thomas Padron-McCarthy
1
指针增量的观点是不正确的。当您增加一个 void * 指针时,您增加的不是 void * 的大小,而是 void 的大小。当然,void 没有大小,所以增量是非法的(在 C89/90 和 C99 中都是如此)。一些编译器允许这种情况作为扩展,将 void * 视为指针算术中的 char *(因此上面写的增量是正确的),但无论如何,这都是一种扩展,而不是标准 C。 - AnT stands with Russia

2
首先,正如其他人已经指出的那样,您正在块的中间声明变量,这只允许在C99中,而不是在C89/90中。也就是说,我们必须得出结论,您正在使用C99。
其次,您以K&R风格定义函数(没有参数类型),但同时未声明稍后的参数类型。这样,您就依赖于“隐式int”规则,该规则在C99中被禁止。也就是说,我们必须得出结论,您使用C99。这已经与“首先”部分矛盾了。(此外,习惯上使用无符号类型来表示“对象大小”的概念。size_t是一种专用类型,通常用于此目的)。
第三,您正在对void *指针进行指针算术运算,这在C89/90和C99中都是非法的。我甚至不知道我们可以从中得出什么结论:)
请确定您要使用的语言,然后我们将从那里开始。

我错过了 size 的隐式声明。 - Michael Burr
谢谢AndreyT,我将使用C89,你可以得出我是一个新手但现在有很多有用的输入 :)。 - Radek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接