对 C 内存池中的内存块进行碎片整理

Defragment Memory Blocks in C Memory Pool

我已经用 C 构建了一个简单的内存池,并且我还实现了在这个池中实现内存块的能力。

内存块本身非常简单,只是一个带有空闲标志和大小属性的双向链表。

我现在想做的是创建一个函数,它接受一个指向我的内存池的指针并对里面的内存块进行碎片整理,以便分配的(空闲 == 0)块朝向池的开头并被释放块在池的尽头。

例如,如果在调用碎片整理函数之前我的内存块结构如下:

Block Size: 25 (41 w/ header), Free: 1
Block Size: 100 (116 w/ header), Free: 0
Block Size: 25 (41 w/ header), Free: 1
Block Size: 100 (116 w/ header), Free: 0
Block Size: 100 (116 w/ header), Free: 0
Block Size: 54 (70 w/ header), Free: 1

然后在调用我的函数之后,这些块将被 ar运行ged 像这样:

Block Size: 100 (116 w/ header), Free: 0
Block Size: 100 (116 w/ header), Free: 0
Block Size: 100 (116 w/ header), Free: 0
Block Size: 25 (41 w/ header), Free: 1
Block Size: 25 (41 w/ header), Free: 1
Block Size: 54 (70 w/ header), Free: 1

我已经尝试构建该函数,但是我 运行 遇到了移动正确块的问题,因为这是我调用函数后的输出:

Block Size: 100 (116 w/ header), Free: 0
Block Size: 25 (41 w/ header), Free: 1
Block Size: 100 (116 w/ header), Free: 0
Block Size: 1744830464 (1744830480 w/ header), Free: 21

我完全不确定该函数在哪里执行了不正确的操作,所以希望有人能为我阐明这一点。

我的碎片整理功能:

void defragment(Pool* pool)
{
    if(pool && pool->root)
    {
        Block* current = pool->root;

        while(current)
        {
            if(!current->free)
            {
                Block* current_prev = current->prev;

                if(current_prev && current_prev->free)
                {
                    Block* prev_prev = current_prev->prev;
                    int new_block_size = current_prev->size;

                    Block* moved_current = memmove(current_prev, current, sizeof(Block) + current->size);

                    if(!moved_current)
                    {
                        printf("couldn't move memory\n");
                    }
                    else
                    {
                        Block* new_block = initBlock((((char*)moved_current) + sizeof(Block) + moved_current->size), new_block_size);
                        new_block->prev = moved_current;
                        new_block->next = moved_current->next;

                        moved_current->prev = prev_prev;
                        moved_current->next = new_block;

                        if(prev_prev)
                        {
                            prev_prev->next = moved_current;
                        }

                        current = moved_current;
                        continue;
                    }
                }
            }

            current = current->next;
        }

        //Join Contiguous Blocks
    }
}

对initBlock函数的调用只需要一个内存地址,将其视为一个Block结构,然后将free属性设置为true,并将size属性设置为给定的大小。

我正在使用带有 -std=C99 标志的 GCC 编译器。

看起来你没有在交换一对块后更新下一个块的 prev 字段。因此,当您前进到下一个块并检查前一个块是否空闲时,您将访问垃圾。你需要像

这样的东西
if (newblock->next)
    new_block->next->prev = new_block;

在上面的 else 部分。

其他问题

  • 如果您的块在池中不是立即连续且按顺序排列,这将导致错误行为。您可能确保整个池是一个连续的内存块,但如果其他一些例程重新排序,您可能仍然 运行 会遇到问题。对于偏执的编程,最好坚持断言以确保不违反这些不变量。
  • 在此操作后,任何指向池的外部指针都将成为垃圾
  • 如果你有奇数大小的块(你看起来是这样),你会得到未对齐的指针,这充其量是低效的,最坏的情况可能会崩溃。
  • 像您描述的那样,池有一个包含指向池的指针的固定大小的关联数组 "handles" 是正常的,并且每当您移动非空闲块时,您都需要更新相应句柄中的指针。手柄也可能有 "lock" 禁止移动方块的标志——在这种情况下,您需要检查该标志并仅移动未锁定的方块,并且当您移动方块时,您可能希望移动到不相邻的方块中。这反过来可能会让您遇到上面第一点的麻烦。