自定义分配器中的碎片。无法正确排序指针

时间:2017-07-17 13:50:29

标签: c pointers memory-management

上下文:我编写了一个最适合的内存分配器。它分配大块内存,并根据请求为程序提供最适合的块。如果内存储备耗尽,它会要求操作系统更多。所有空闲块都存储在链接列表中,按增加指针值排序。

问题::当释放内存时,程序应该将其链接回空闲的块列表以供回收,更重要的是,如果可能的话,将给定块与其周围的块合并。不幸的是,只要我不需要操作系统提供超过一个超级块来服务,这只能很好地工作。当发生这种情况时,我收到的新块具有无意义的寻址,并在其他超块寻址空间之间插入。这会导致永久性的碎片化。

Tl; dr :我正在获得新的超级内存块,其地址位于另一个超级块的地址空间内,导致返回子块时出现碎片。

问题说明

这是我上面描述的问题图。

  • 数字:内存地址(来自实际执行)。
  • 米色块:自由记忆。
  • 白色块:内存已取消链接以供使用。

该图显示了碎片催化剂发生之前的内存使用情况。您可以看到一旦块被插入,重新签入后,繁忙的块将永远无法合并。

代码:可重现的示例: 以下包括分配器和小型测试程序。它必须至少编译为C99。

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/*****************************************************************************/
/*                          SYMBOLIC CONSTANTS                               */
/*****************************************************************************/

#define NEXT(hp)                ((hp)->key.next)
#define UNITS(hp)               ((hp)->key.units)
#define UNIT_SIZE               sizeof(Header)

#define MIN_UNITS_ALLOC         64
#define MAX(a,b)                ((a) > (b) ? (a) : (b))

/*****************************************************************************/
/*                          TYPE DEFINITIONS                                 */
/*****************************************************************************/

typedef union header {
    intmax_t align;
    struct {
        union header *next;
        unsigned units;
    } key;
} Header;

/*****************************************************************************/
/*                          GLOBAL VARIABLES                                 */
/*****************************************************************************/

Header base = {.key = { NULL, 0 }};
Header *list;

/*****************************************************************************/
/*                              PROTOTYPES                                   */
/*****************************************************************************/

/* Allocates a 'bytes' size block of memory. On success, returns pointer to
 * the block. On error, NULL is returned. */
void *alloc (size_t bytes);

/* Returns a 'bytes' size block of allocated memory for reuse */
void release (void *bytes);

/* Attempts to reserve memory from the operating system via a system call */
static Header *reserve (unsigned units);

/*****************************************************************************/
/*                      FUNCTION IMPLEMENTATIONS                             */
/*****************************************************************************/


void *alloc (size_t bytes) {
    size_t units, diff;
    Header *block, *lastBlock, *best, *lastBest;

    if (bytes == 0) {
        return NULL;
    }

    if (list == NULL) {
        list = base.key.next = &base;
    }

    best = lastBest = NULL;
    units = (bytes + UNIT_SIZE - 1) / UNIT_SIZE + 1;
    diff = SIZE_MAX;

    for (lastBlock = list, block = NEXT(list); ; lastBlock = block, block = NEXT(block)) {

        /* Loop across list, find closest fitting block */
        if (UNITS(block) >= units && UNITS(block) - units < diff) {
            diff = UNITS(block) - units;
            best = block;
            lastBest = lastBlock;
        }

        /* Upon cycle completion */
        if (block == list) {

            /* If no block available, reserve some. */
            if (best == NULL) {
                if ((lastBest = reserve(units)) == NULL) {
                    return NULL;
                } else {
                    fprintf(stderr, "\nalloc: Out of memory, linking new block %lld of size %u.\n\n", (long long)lastBest, UNITS(lastBest));
                    release((void *)(lastBest + 1));
                }

            /* If block of perfect size, return. Else slice and return */
            } else {
                if (diff == 0) {
                    NEXT(lastBest) = NEXT(best);
                } else {
                    UNITS(best) = diff;
                    best += diff;
                    UNITS(best) = units;
                }
                fprintf(stderr, "alloc: Unlinked block %lld of %u units.\n", (long long)best, UNITS(best));
                return (void *)(best + 1);
            }
        }
    }
}

void release (void *bytes) {
    Header *p, *block;

    block = (Header *)bytes - 1;

    /* Choose p such that: p -> block -> NEXT(p) */
    for (p = list; !(p < block && NEXT(p) > block); p = NEXT(p)) {
        if (p >= NEXT(p) && (block > p || block < NEXT(p))) {
            break;
        }
    }

    /* Merge block with NEXT(p) if adjacent */
    if (block + UNITS(block) == NEXT(p)) {
        NEXT(block) = NEXT(NEXT(p));
        UNITS(block) += UNITS(NEXT(p));
    } else {
        NEXT(block) = NEXT(p);
    }

    /* Merge block with p if adjacent */
    if (p + UNITS(p) == block) {
        NEXT(p) = NEXT(block);
        UNITS(p) += UNITS(block);
    } else {
        NEXT(p) = block;
    }

}

static Header *reserve (unsigned units) {
    char *bytes, *sbrk(int);
    Header *block;
    units = MAX(units, MIN_UNITS_ALLOC);

    if ((bytes = sbrk(units)) == (char *)-1) {
        return NULL;
    } else {
        block = (Header *)bytes;
        UNITS(block) = units;
    }
    return block;
}

/*****************************************************************************/
/*                      TESTING FUNCTIONS (DELETE)                           */
/*****************************************************************************/


void printFreeList (unsigned byAddress) {
    Header *lp = list;

    if (lp == NULL) {
        fprintf(stdout, "List is NULL\n");
        return;
    }

    do {
        fprintf(stdout, "[ %lld ] -> ", (byAddress ? (long long)lp : (long long)UNITS(lp)));
        lp = NEXT(lp);
    } while (lp != list);
    putc('\n', stdout);
}

void releaseItemAtIndex (int i, int k, long long *p[]) {
    fprintf(stderr, "Releasing block %d/%d\n", i, k);
    release(p[i]);
    for (int j = i; j < k; j++) {
        if (j + 1 < k) {
            p[j] = p[j + 1];
        }
    }
}

#define MAX_TEST_SIZE           5000

int main (int argc, const char *argv[]) {

    /* Seed PRNG: For removed random deletion (now manual) */
    srand(time(NULL));

    /* Array of pointers to store allocated blocks, blockSize we want to allocate. */
    long long *p[MAX_TEST_SIZE], blockSize = 256;

    /* Number of blocks we choose to allocate */
    int k = 6;

    /* Allocate said blocks */
    for (int i = 0; i < k; i++) {
        p[i] = alloc(blockSize * sizeof(char));
        printFreeList(0); printFreeList(1); putchar('\n');
    }

    fprintf(stderr, "\n\n");

    int idx;
    while (k > 0) {
        fprintf(stderr, "Delete an index between 0 up to and including %d:\n", k - 1);
        scanf("%d", &idx);
        releaseItemAtIndex(idx, k, p);
        printFreeList(0); printFreeList(1); putchar('\n');
        k--;
    }

    return 0;
}

杂项详情

  • 我正在运行64位操作系统。
  • 我不知道堆上的指针比较是否保证有效。根据K&amp; R的标准不能保证这一点。

1 个答案:

答案 0 :(得分:0)

好吧,我最终在几年后写了一篇似乎还不错的书。奖励:它使用的是静态内存。


头文件

#if !defined(STATIC_ALLOCATOR_H)
#define STATIC_ALLOCATOR_H


/*
 *******************************************************************************
 *                        (C) Copyright 2020                                   *
 * Created: 07/04/2020                                                         *
 *                                                                             *
 * Programmer(s):                                                              *
 * - Jillian Oduber                                                            *
 * - Charles Randolph                                                          *
 *                                                                             *
 * Description:                                                                *
 *  Static first-fit memory allocator                                          *
 *                                                                             *
 *******************************************************************************
*/


#include <iostream>
#include <cstddef>
#include <cstdint>
#include "dx_types.h"

extern "C" {
    #include "stddef.h"
}


/*
 *******************************************************************************
 *                              Type Definitions                               *
 *******************************************************************************
*/


// Defines the header block used in the custom allocator
typedef union block_h {
    struct {
        union block_h *next;
        size_t size; // Size is in units of sizeof(block_h)
    } d;
    max_align_t align;
} block_h;



/*
 *******************************************************************************
 *                              Class Definitions                              *
 *******************************************************************************
*/


class Static_Allocator {
public:


    /*\
     * @brief Configures the class with the given static memory capacity
     * @param memory Pointer to static array (must support address comparison)
     * @param size   Total number of bytes alloted for distribution
    \*/
    Static_Allocator(uint8_t *memory, size_t size);

    /*\
     * @brief Returns a memory block of the desired size
     * @param size Number of bytes to allocate
     * @return 
     *  - valid pointer if memory is available
     *  - NULL otherwise
    \*/
    uint8_t *alloc (size_t size);


    /*\
     * @brief Releases the allocated block of memory
     * @param ptr Pointer to allocated block of memory
     * @return 
     *  - STATUS_OK on success
     *  - STATUS_ERR if invalid block 
     *  - STATUS_BAD_PARAM on NULL parameter
    \*/
    dx::status_t free (uint8_t *ptr);


    /*\
     * @brief Returns the amount of free memory
     * @return size_t Bytes (of available free memory)
    \*/
    size_t free_memory_size ();


    /*\
     * @brief Debug utility which shows what is on the free-list
     * @return None
    \*/
    void show ();

    /*\
     * @brief Debug utility which asserts there is only a single
     *        memory block
    \*/
    bool unified ();


private:

    // Fixed allocator capacity 
    size_t d_capacity;

    // Fixed memory 
    uint8_t *d_memory;

    // Pointer to head of the linked list
    block_h *d_free_list;

    // Available memory
    size_t d_free_memory_size;

    // Unit size
    static const size_t mem_unit_size = sizeof(block_h);

};


#endif

源文件

#include "static_allocator.h"


Static_Allocator::Static_Allocator (uint8_t *memory, size_t size):
    d_capacity(0),
    d_memory(NULL),
    d_free_list(NULL),
    d_free_memory_size(0)
{
    // Assign memory array
    d_memory = memory;

    // Trim off two mem_unit_size for head + init-block + spillover
    d_capacity = size - (size % mem_unit_size) - 2 * mem_unit_size;

    // Ensure free memory is set to capacity
    d_free_memory_size = d_capacity;
}


uint8_t * Static_Allocator::alloc (size_t size)
{
    block_h *last, *curr;

    // Number of blocks sized units to allocate
    size_t nblocks = (size + mem_unit_size - 1) / mem_unit_size + 1;

    // If larger than total capacity or invalid size; immediately return
    if ((nblocks * sizeof(block_h)) > d_capacity || size == 0) {
        return NULL;
    }

    // If uninitialized, create initial list units
    if ((last = d_free_list) == NULL) {
        block_h *head = reinterpret_cast<block_h *>(d_memory);
        head->d.size = 0;

        block_h *init = head + 1;
        init->d.size = d_capacity / sizeof(block_h);
        init->d.next = d_free_list = last = head;

        head->d.next = init;
    }

    // Problem: You must not be allowed to merge the init block

    // Look for space - stop if wrapped around
    for (curr = last->d.next; ; last = curr, curr = curr->d.next) {

        // If sufficient space available
        if (curr->d.size >= nblocks) {

            if (curr->d.size == nblocks) {
                last->d.next = curr->d.next;
            } else {
                curr->d.size -= nblocks;
                curr += curr->d.size; // Suspect
                curr->d.size = nblocks;
            }

            // Reassign free list head
            d_free_list = last;

            // Update amount of free memory available
            d_free_memory_size -= nblocks * sizeof(block_h);

            return reinterpret_cast<uint8_t *>(curr + 1);
        }

        // Otherwise not sufficient space. If reached
        // the head of the list again, there is no more
        // memory left
        if (curr == d_free_list) {
            return NULL;
        }
    }
}


dx::status_t Static_Allocator::free (uint8_t *ptr)
{
    block_h *b, *p;

    // Check if parameter is valid
    if (ptr == NULL) {
        std::cerr << "Null pointer!";
        return dx::STATUS_BAD_PARAM;
    }

    // Check if memory is in range
    if (!(ptr >= (d_memory + 2 * mem_unit_size) 
        && ptr < (d_memory + d_capacity + 2 * mem_unit_size))) {
        std::cerr << "Pointer out of range!";
        return dx::STATUS_ERR;
    }

    // Obtain block header (ptr - sizeof(block_h))
    b = reinterpret_cast<block_h *>(ptr) - 1;


    // Update available memory size
    d_free_memory_size += b->d.size; 

    // Find insertion location for block
    for (p = d_free_list; !(b >= p && b < p->d.next); p = p->d.next) {

        // If the block comes at the end of the list - break
        if (p >= p->d.next && b > p) {
            break;
        }

        // If at the end of the list, but block comes before next link
        if (p >= p->d.next && b < p->d.next) {
            break;
        }
    }

    // [p] <----b----> [p->next] ----- [X]

    // Check if we can merge forwards
    if (b + b->d.size == p->d.next) {
        b->d.size += (p->d.next)->d.size;
        b->d.next = (p->d.next)->d.next;
    } else {
        b->d.next = p->d.next;
    }

    // Check if we can merge backwards
    if (p + p->d.size == b) {
        p->d.size += b->d.size;
        p->d.next = b->d.next;
    } else {
        p->d.next = b;
    }

    d_free_list = p;

    return dx::STATUS_OK;
}


size_t Static_Allocator::free_memory_size () 
{
    return d_free_memory_size;
}


void Static_Allocator::show ()
{
    std::cout << "Block Size: " << sizeof(block_h) << "\n"
         << "Capacity: " << d_capacity << "\n"
         << "Free Size (B): " << d_free_memory_size << "\n";

    if (d_free_list == NULL) {
        std::cout << "<uninitialized>\n";
        return;
    }

    // Show all blocks
    block_h *p = d_free_list;
    do {
        std::cout << "-------------------------------\n";
        std::cout << "Block Address: " << (uint64_t)(p) << "\n";
        std::cout << "Blocks (32B): " << p->d.size << "\n";
        std::cout << "Next: " << (uint64_t)(p->d.next) << "\n";
        std::cout << "{The next block is " << (uint64_t)((p->d.next) - (p)) << " bytes away, but we claim the next " << p->d.size * mem_unit_size << " bytes\n";
        p = p->d.next;
    } while (p != d_free_list);
    std::cout << "-------------------------------\n\n\n";
}


bool Static_Allocator::unified () {
    if (d_free_list == NULL) {
        return false;
    }
    return ((d_free_list->d.next)->d.next == d_free_list);
}