如何在 C 语言中使用正确的指针对齐对 malloc() 实现进行编程

How to program a malloc() implementation with correct pointer alignment in C

提问人:Virgil G. 提问时间:10/14/2023 最后编辑:trincotVirgil G. 更新时间:11/11/2023 访问量:172

问:

我目前正在尝试用 C 语言重新编码我自己的实现。我遵循了几个教程,但每次指针对齐都是不正的(对齐为 4 的幂),但根据 ABI,有必要返回一个与 8 的幂对齐的指针。所以我重新改编了一个。我重新编码了 , , , , .malloc()malloc()malloc()free()calloc()realloc()reallocf()

测试时,我遇到了 valgrind 的无声错误。进一步测试(多次调用 , , )时,我得到一个段错误。malloc()malloc()realloc()free()

代码如下:

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

typedef struct block_s *block_t;

    struct block_s {
        size_t size;
        block_t next;
        block_t prev;
        int free;
        void *ptr;
        char data[1];
    };


#define BLOCK_SIZE 36

#define align8(x) (((((x) - 1) >> 3) << 3) + 8)

void *base = NULL;

block_t get_block(void *p)
{
    char *tmp;
    tmp = p;
    return (p = tmp -= BLOCK_SIZE);
}

int valid_addr(void *p)
{
    if (base) {
        if (p > base && p < sbrk(0))
            return (p == (get_block(p))->ptr);
    }
    return (0);
}

block_t fusion(block_t b)
{
    if (b->next && b->next->free) {
        b->size += BLOCK_SIZE + b->next->size;
        b->next = b->next->next;
        if (b->next)
            b->next->prev = b;
    }
    return (b);
}

void split_block(block_t b, size_t s)
{
    block_t new;
    new = (block_t) b->data + s;
    new->size = b->size - s - BLOCK_SIZE;
    new->next = b->next;
    new->prev = b;
    new->free = 1;
    new->ptr = new->data;
    b->size = s;
    b->next = new;
    if (new->next)
        new->next->prev = new;
}

block_t find_block(block_t *last, size_t size)
{
    block_t b = sbrk(0);
    while (b && !(b->free && b->size >= size))
    {
        *last = b;
        b = b->next;
    }
    return (b);
}

block_t extend_heap(block_t last, size_t s)
{
    int64_t sb;
    block_t b;
    b = sbrk(0);
    sb = (size_t) sbrk(BLOCK_SIZE + s);
    if (sb < 0)
        return (NULL);
    b->size = s;
    b->next = NULL;
    b->prev = last;
    b->ptr = b->data;
    if (last) {
        last->next = b;
    }
    b->free = 0;
    return (b);
}

void copy_block(block_t src, block_t dest)
{
    size_t *sdata, *ddata;
    size_t i;
    sdata = src->ptr;
    ddata = dest->ptr;
    for (i = 0; (i * 8) < src->size && (i * 8) < dest->size; i++)
        ddata[i] = sdata[i];
}

void my_free(void *p)
{
    block_t b;
    if (valid_addr(p)) {
        b = get_block(p);
        b->free = 1;
        if (b->prev && b->prev->free) {
            b = fusion(b->prev);
        }
        if (b->next) {
            fusion(b);
        } else {
            if (b->prev)
                b->prev->next = NULL;
            else
                base = NULL;
            brk(b);
        }
    }
}

void *my_malloc(size_t size)
{
    block_t b, last;
    size_t s;
    s = align8(size);
    if (base) {
        last = base;
        b = find_block(&last, s);
        if (b) {
            if ((b->size - s) >= (BLOCK_SIZE + 8))
                split_block(b, s);
            b->free = 0;
        } else {
            b = extend_heap(last, s);
            if (!b)
                return (NULL);
        }
    } else {
        b = extend_heap(NULL, s);
        if (!b)
            return (NULL);
        base = b;
    }
    return (b->data);
}

void *my_calloc(size_t number, size_t size)
{
    size_t *new, s8, i;
    new = my_malloc(number * size);
    if (new) {
        s8 = align8(number * size) << 3;
        for (i = 0; i < s8; i++)
            new[i] = 0;
    }
    return (new);
}

void *my_realloc(void *p, size_t size)
{
    size_t s;
    block_t b, new;
    void *newp;
    if (!p) {
        return (my_malloc(size));
    }
    if (valid_addr(p)) {
        s = align8(size);
        b = get_block(p);
        if (b->size >= s) {
            if (b->size - s >= (BLOCK_SIZE + 8)) {
                split_block(b, s);
            }
        } else {
            if (b->next && b->next->free && (b->size + BLOCK_SIZE + b->next->size) >= 5) {
                fusion(b);
                if (b->size - s >= (BLOCK_SIZE + 8)) {
                    split_block(b, s);
                }
            } else {
                newp = my_malloc(s);
                if (!newp) {
                    return (NULL);
                }
                new = get_block(newp);
                copy_block(b, new);
                my_free(p);
                return (newp);
            }
        }
        return (p);
    }
    return (NULL);
}

void *my_reallocf(void *p, size_t size)
{
    void *newp;
    newp = my_realloc(p, size);
    if (!newp)
        my_free(p);
    return (newp);
}

以下是 malloc 的测试代码:

int main(void)
{
    char *str = my_malloc(15 * sizeof(char));
    str = strcpy(str, "Hello World!\0");
    printf("# Malloc test\nString %p = %s\n", str, str);
    my_free(str);
}

这是 valgrind 报告:

==56698== Memcheck, a memory error detector
==56698== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==56698== Using Valgrind-3.21.0 and LibVEX; rerun with -h for copyright info
==56698== Command: ./build/my_malloc
==56698== 
==56698== Invalid write of size 8
==56698==    at 0x1098B3: main (my_malloc.c:214)
==56698==  Address 0x403502d is in the brk data segment 0x4035000-0x4035033
==56698== 
==56698== Invalid read of size 1
==56698==    at 0x4847ED4: strlen (vg_replace_strmem.c:501)
==56698==    by 0x48E36E8: __printf_buffer (vfprintf-process-arg.c:435)
==56698==    by 0x48E3E51: __vfprintf_internal (vfprintf-internal.c:1523)
==56698==    by 0x48D92FE: printf (printf.c:33)
==56698==    by 0x1098D9: main (my_malloc.c:215)
==56698==  Address 0x4035034 is 0 bytes after the brk data segment limit 0x4035034
==56698== 
# Malloc test
String 0x4035028 = Hello World!
==56698== 
==56698== HEAP SUMMARY:
==56698==     in use at exit: 0 bytes in 0 blocks
==56698==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==56698== 
==56698== All heap blocks were freed -- no leaks are possible
==56698== 
==56698== For lists of detected and suppressed errors, rerun with: -s
==56698== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

此外,我注意到 Valgrind 在我的各种测试中返回了错误的值(在分配和释放的内存上)......我对这个问题没有假设。

选择不使用是经过深思熟虑的。mmap()

我还想指出,我的动态内存分配器适用于没有标准 C 库的环境。

我的假设是我错误地计算了块大小和/或未对齐我的指针。

通过将 更改为 44,测试与简单的 malloc / free 工作,但一旦有几个它就段错误。BLOCK_SIZE

你能告诉我如何使这个分配器工作吗?

提前致谢

c malloc 堆内存

评论

3赞 Barmar 10/14/2023
为什么 64 位和 32 位的实现不同?
2赞 فِرجِيل 10/14/2023
@Barmar我读过,对于 32 位系统,指针必须对齐为 4 的幂,对于 64 位系统,指针必须对齐为 8 的幂。
1赞 Barmar 10/14/2023
好点子。这似乎应该是一个非常小的变化。
1赞 Craig Estey 10/14/2023
与你可能有一个问题。使用可能会更容易char data[1];char data[];
4赞 SoronelHaetir 10/14/2023
实际上,即使在 32 位构建中,您可能仍应该与 8 字节边界对齐,以便双精度自然对齐。

答: 暂无答案