Как вызвать выровненный новый / удалить правильно? - PullRequest
0 голосов
/ 25 декабря 2018

Как мне вызвать new оператор с выравниванием?

auto foo = new(std::align_val_t(32)) Foo; //?

, а затем, как его правильно удалить?

delete(std::align_val_t(32), foo); //?

Если это правильная форма использования этихперегрузки, почему valgring жалуется на несоответствие free () / delete / delete []?

1 Ответ

0 голосов
/ 25 декабря 2018

существует очень простой принцип - подпрограмма без памяти всегда должна совпадать, чтобы выделить подпрограмму.если мы используем несоответствие размещения и свободного времени, поведение может быть любым: все может быть случайным, нормально, или сбой по времени выполнения, или утечка памяти, или повреждение кучи.

если мы выделим память с выровненной версией оператора new

void* operator new  ( std::size_t count, std::align_val_t al);

мы должны использовать соответствующую выровненную версию оператора delete

void operator delete  ( void* ptr, std::align_val_t al );

вызов void operator delete ( void* ptr ); здесь всегда должен приводить к ошибке во время выполнения.давай просто протестируем

    std::align_val_t al = (std::align_val_t)256;
    if (void* pv = operator new(8, al))
    {
        operator delete(pv, al);
        //operator delete(pv); this line crash, or silently corrupt heap
    }

почему выровненная и не выровненная версия operator delete всегда несовместима?давай подумаем - как можно выделить align на какое-то значение памяти?мы изначально всегда выделяем некоторый блок памяти.для возврата align указатель для использования - нам нужно отрегулировать выделенный указатель памяти для многократного выравнивания.Хорошо.это возможно, если выделить больше памяти, чем запрошено, и настроить указатель.но теперь вопрос - насколько свободен этот блок?вообще пользователь получил указатель не на начало выделенной памяти - как с этого указателя пользователя вернуться назад к началу выделенного блока?без дополнительной информации это невозможно.нам нужно сохранить указатель на фактически выделенную память, прежде чем пользователь вернет указатель.может быть, это будет более заметно в типичной реализации кода для выровненных new и delete использования _aligned_malloc и _aligned_free

void* operator new(size_t size, std::align_val_t al)
{
    return _aligned_malloc(size, static_cast<size_t>(al));
}

void operator delete  (void * p, std::align_val_t al)
{
    _aligned_free(p);
}

прине выровнены new и delete используйте malloc и free

void* operator new(size_t size)
{
    return malloc(size);
}

void operator delete  (void * p)
{
    free(p);
}

теперь давайте посмотрим на внутреннюю реализацию _aligned_malloc и _aligned_free

void* __cdecl _aligned_malloc(size_t size, size_t alignment)
{
    if (!alignment || ((alignment - 1) & alignment))
    {
        // alignment is not a power of 2 or is zero
        return 0;
    }

    union {
        void* pv;
        void** ppv;
        uintptr_t up;
    };

    if (void* buf = malloc(size + sizeof(void*) + --alignment))
    {
        pv = buf;
        up = (up + sizeof(void*) + alignment) & ~alignment;
        ppv[-1] = buf;

        return pv;
    }

    return 0;
}

void __cdecl _aligned_free(void * pv)
{
    if (pv)
    {
        free(((void**)pv)[-1]);
    }
}

в общих словах _aligned_malloc выделить size + sizeof(void*) + alignment - 1 вместо того, чтобы запрашивать вызывающий size.отрегулируйте выделенный указатель в соответствии с выравниванием, и сохраните первоначально выделенную память, прежде чем указатель вернется к вызывающей стороне.

и _aligned_free(pv) вызывают не free(pv), а free(((void**)pv)[-1]); - для всегда другой указатель.потому что этот эффект _aligned_free(pv) всегда другое сравнение free(pv)operator delete(pv, al); всегда несовместимы с operator delete(pv);, если, скажем, delete [] обычно имеют тот же эффект, что и delete, но выравнивание и не выравнивание всегда отличаются по времени выполнения.

...