Безопасно ли "освобождать" память, если она распределена через перегруженный `new []`, который делегирует `malloc`?

Мой вопрос не является дубликатом Безопасно ли` free() `память, выделенная` new`?.

Я пишу сборщик мусора для POD, в котором я определяю свои собственные operator new/new[] и operator delete/delete[]. Код ниже:

#include <iostream>
#include <map>

std::map<void*, std::size_t> memory; // globally allocated memory map
struct collect_t {} collect; // tag for placement new

void* operator new(std::size_t size, const collect_t&)
{
    void* addr = malloc(size);
    memory[addr] = size;
    return addr;
}

void* operator new[](std::size_t size, const collect_t&) 
{
    return operator new(size, collect);
}

void operator delete(void *p, const collect_t&) noexcept
{
    memory.erase(p); // should call ::operator delete, no recursion
    free(p);
}

void operator delete[](void *p, const collect_t&) noexcept
{
    operator delete(p, collect);
}

void display_memory()
{
    std::cout << "Allocated heap memory: " << std::endl;
    for (auto && elem : memory)
    {
        std::cout << "\tADDR: " << elem.first << " "
                  << "SIZE: "  << elem.second << std::endl;
    }
}

void clear()
{
    for (auto && elem : memory)
        free(elem.first); // is this safe for arrays?
    memory.clear();
}

int main()
{
    // use the garbage collector
    char *c = new(collect) char; 
    int *p = new(collect) int[1024]; // true size: sizeof(int)*1024 + y (unknown overhead)

    display_memory();
    clear();
    display_memory();
}

Идея проста: я сохраняю все выделенные отслеживаемые адреса (те, которые были выделены моим пользовательским new) в std::map, и убедитесь, что в конце дня я очищаю всю память в своем clear() функция. Я использую тег для моих new и delete (и не перегружаю глобальные), так что std::map allocator может вызывать глобальные без повторения.

Мой вопрос следующий: в моей функции clear() я де-выделяю память в строке

for (auto && elem : memory)
    free(elem.first); // is this safe for arrays?

Это безопасно для массивов, например. для int *p = new(collect) int[1024];?. Я считаю, что это так, поскольку void* operator new[](std::size_t size, const collect_t&) вызывает operator new(size, collect);, а последний вызывает malloc. Я не уверен на 100%, хотя, может здесь что-то не так?

Ответ 1

Мне кажется, что для того, чтобы память находилась в вашем контейнере memory, он должен был быть выделен вашим настраиваемым распределителем, который всегда вызывает malloc. Поэтому я считаю, что ваш бесплатный вызов кода должен быть в порядке.

Очевидно, что если кто-то собирается набивать случайные адреса на карту памяти, вы будете иметь всевозможные поведения undefined.

Ответ 2

Предполагая, что объекты, использующие ваш сборщик мусора, никогда не реализуют деструктор, и это справедливо для любых членов, которые могут содержать эти объекты, как бы "безопасный" код в том смысле, что вызов free() напрямую пропустив работу, которую компилятор выполнил бы для достижения того же самого, что и встраивание вызовов delete.

Однако код не очень безопасен.

Если вы когда-либо изменили способ работы вашего сборщика мусора или как работала функция new, вам придется выслеживать все прямые вызовы на free(), чтобы устранить любые проблемы. Если код когда-либо был вырезан и вставлен или иным образом повторно использован в контексте вне вашего сборщика мусора, вы столкнулись бы с аналогичной проблемой.

Лучше всего всегда придерживаться new до delete и malloc до free.