自定义malloc()实现头设计

时间:2009-11-09 13:11:45

标签: c memory memory-management malloc allocation

我正在尝试在C中编写一个用于调试目的的自定义分配器(作为练习),其中我将使用单个链表来使用First Fit算法将自由列表保存在一起。我在下面展示了我想在“空内存节点”中创建的结构。

如何在内存的前几个字节处编写头块(一个特定的联合),我得到(我使用malloc()来初始获得一块内存)以便剩余的字节是免费的?

这是我正在使用的联盟:

/*Define Header Structure for proper alignment*/
union header {
struct{
    union header* next;
    unsigned size ; /*Make it size_t*/
}s; 
double dummy_align_var;
};

-------------------------------------------------------------------------------
|Next        |Size of  |16Byte| User is concerned only about |16Byte|         |
|Free Memory |Allocated|Header| this portion  of memory      |Footer|Checksum |
|Address     |Block    |Picket| and has no knowledge of rest |Picket|         |
-------------------------------------------------------------------------------
|-------Header---------|      ^Address Returned to user
                              ^------User Requested Size-----^
^-------------Memory Obtained From The Operating System-----------------------^
*/

[编辑] 根据提供的建议更改了块结构。

7 个答案:

答案 0 :(得分:3)

对于调试malloc,请考虑在控制结构和用户数据的开头之间以及用户数据的结尾和校验和之间放置填充空间。填充的一个字节应该是零字节0x00 - 所以字符串操作停止;考虑将另一个设为0xFF。如果你有一个固定的模式并发现它已经发生了变化,那你就知道有些事情已经超出界限 - 但你的敏感控制数据更有可能没有被践踏。如果在分配给用户的空间的任一侧使用16字节的填充,则可能会将4个字节的零点适当地对齐(因此为零4字节整数),并且可能为0xFFFFFFFF为-1。此外,由于您可能会将请求的大小四舍五入到基本块大小的倍数,因此请将用户不使用的字节设置为已知值 - 并验证它们保持不变。这将检测“超过分配长度的一个”的修改,或仅检测分配长度上的几个字节,否则将无法检测到。

填充中零字节的唯一缺点是,在查找空字节时,您不会轻易检测到在分配的内存末尾没有停止的读取操作。您可以通过使用填充而没有零字节的替代选项来深入了解这些选项。

要考虑的另一个选择是尝试将控制数据与返回给用户的内存完全分开。当然,完全分离是不可能的,但至少要保持一个分配列表(大小和指针)与分配的块分开。同样,这可以让您的宝贵控制数据远离不受控制的内存践踏操作,从而为您提供保护。您没有完全受到错误指针的保护,但您可以得到更好的保护。 (并且您仍然可以在分配的空间周围提供缓冲区来检测失控写入。)但是,这种设计与问题明显不同。


假设你从'malloc()'得到了你的内存块,那么你会做 - 粗略地说:

void *my_malloc(size_t nbytes)
{
    size_t reqblocks = (nbytes + sizeof(header) - 1) / sizeof(header);
    size_t reqspace  = (reqblocks + 2) * sizeof(header) + 2 * sizeof(padding);
    void *space = malloc(reqspace);
    if (space == 0)
        return space;
    void *retval = (char *)space + sizeof(header) + sizeof(padding);
    header *head = space;
    head->next = ...next...;
    head->size = nbytes;
    ...set head padding to chosen value...
    ...set tail padding to chosen value...
    ...set gap between nbytes and block boundary to chosen value...
    return retval;
}

还有一些解释要做......

答案 1 :(得分:2)

你为什么使用工会?只需使用struct并将&dummy_align_var作为空闲块的开头返回给用户。

哦,因为这是为了调试,我建议你添加一个mungwall:在用户区的两边放16个字节并用一些模式填充它们(例如0xdeadbeef,重复四次)。在free()期间检查这些字节是否没有改变。

[编辑]这是一些伪代码:

struct header {
    struct header * next;
    unsigned size;
    // put mungwall here
    double user_data;
};

init()
    int blockSize = 1024;
    char * bigMem = original_malloc(blockSize);
    struct header * first = (struct header *)bigMem;
    first->next = NULL;
    first->size = blockSize - (sizeof(struct header) - sizeof(double));

答案 2 :(得分:2)

我会做像

这样的事情
#define MEM_ALIGN 4 // 8 on 64b eventually

struct header {
    union aligned_header {
        struct _s {
            union aligned_header* next;
            size_t size;
        } data;
        char dummy_align_var[sizeof(struct _s) + sizeof(struct _s)%MEM_ALIGN];
    } header_data;
    char user_address;
};

并返回&user_address

答案 3 :(得分:1)

您可能还希望将dummy_align_var声明为union header* prev,以便可以将空闲内存块放在双向链表中。

当你想要将一个已释放的块与前一个和下一个黑色合并时,如果它们也是免费的,那么这对性能有很大帮助。

最后,你没有提到它,保持按大小排序的列表使得找到为给定请求分配的最佳块更快,而在地址上排序使得更容易合并释放的块。如果要同时执行这两项操作,请将用户部分设置为至少3 header*大,这将适合释放块时所需的指针。

除了Aaron提到的边界外,用相同的模式覆盖释放的缓冲区。这样可以更轻松地识别使用已释放缓冲区的代码。

答案 4 :(得分:1)

我建议它会很有用: 几年前我需要备份malloc()工具以进行调试(分配跟踪器等)...而且很容易从libstdc中获取FreeBSD实现。这是我记得FreeBSSD 5.0甚至4.x晚期版本,但有趣的是他们的设施被简单的库malloc.o模块隔离,所以这层的重载非常简单copy'n'paste,实现真的很好。< / p>

你真的要做所有这些工作吗?是的,这只是检查,我不假装这个解决方案是最好的。

答案 5 :(得分:1)

如果需要,您可以使用原始联盟:

union header *hdr = malloc(total_size);
void *user_ptr = hdr + 1;
char *trailer_ptr = (char *)user_ptr + user_size;

如果将user_ptr ed块视为这些联合的数组,则会将union header设置为下一个malloc开始的位置。这就是您返回给用户的价值。

它还将trailer_ptr设置为指向用户分配后的第一个字节,您可以在其中放置校验和。

答案 6 :(得分:0)

如果您不想使用malloc(),您应该查看sbrk