Оператор новой перегрузки и выравнивания

Я перегружаю operator new, но я недавно столкнулся с проблемой с выравниванием. В основном у меня есть класс IBase который обеспечивает operator new а также delete во всех необходимых вариантах. Все классы происходят от IBase и, следовательно, также использовать пользовательские распределители.

Проблема, с которой я сталкиваюсь сейчас, заключается в том, что у меня есть ребенок Foo который должен быть выровнен по 16 байтов, в то время как все остальные в порядке, если выровнены по 8 байтов. Однако мой распределитель памяти выравнивается по 8-байтовым границам только по умолчанию, поэтому теперь код в IBase::operator new возвращает неиспользуемый кусок памяти. Как это должно быть решено правильно?

Я могу просто принудительно распределить все выделения до 16 байтов, что будет работать до тех пор, пока не появится всплывающий 32-байтовый тип. Выяснение выравнивания внутри operator new не кажется тривиальным (могу ли я сделать там виртуальный вызов функции для получения фактического выравнивания?) Каков рекомендуемый способ справиться с этим?

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

3 ответа

Решение

Это возможное решение. Он всегда будет выбирать оператора с самым высоким выравниванием в данной иерархии:

#include <exception>
#include <iostream>
#include <cstdlib>

// provides operators for any alignment >= 4 bytes
template<int Alignment>
struct DeAllocator;

template<int Alignment>
struct DeAllocator : virtual DeAllocator<Alignment/2> {
  void *operator new(size_t s) throw (std::bad_alloc) {
    std::cerr << "alignment: " << Alignment << "\n";
    return ::operator new(s);
  }

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

template<>
struct DeAllocator<2> { };

// ........... Test .............
// different classes needing different alignments
struct Align8 : virtual DeAllocator<8> { };
struct Align16 : Align8, virtual DeAllocator<16> { };
struct DontCare : Align16, virtual DeAllocator<4> { };

int main() {
  delete new Align8;   // alignment: 8
  delete new Align16;  // alignment: 16
  delete new DontCare; // alignment: 16
}

Он основан на правиле доминирования: если в поиске есть неоднозначность, и неоднозначность находится между именами производного и виртуального базового класса, вместо него берется имя производного класса.


Возникли вопросы почему DeAllocator<I> наследуется DeAllocator<I / 2>, Ответ заключается в том, что в данной иерархии могут быть разные требования к выравниванию, предъявляемые классами. Представь это IBase не имеет требований к выравниванию, A имеет 8-байтовое требование и B имеет 16-байтовое требование и наследует A:

class IBAse { };
class A : IBase, Alignment<8> { };
class B : A, Alignment<16> { };

Alignment<16> а также Alignment<8> оба выставляют operator new, Если вы сейчас говорите new B, компилятор будет искать operator new в B и найдет две функции:

            // op new
            Alignment<8>      IBase
                 ^            /
                  \         /
                    \     /
 // op new            \ /
 Alignment<16>         A
            \         /
              \     /
                \ /
                 B 

B ->      Alignment<16>  -> operator new
B -> A -> Alignment<8> -> operator new

Таким образом, это было бы неоднозначно, и мы не смогли бы скомпилировать: ни один из них не скрывает другой. Но если вы теперь наследуете Alignment<16> практически из Alignment<8> и сделать A а также B наследовать их практически, operator new в Alignment<8> будет скрыто:

            // op new
            Alignment<8>      IBase
                 ^            /
                / \         /
              /     \     /
 // op new  /         \ /
 Alignment<16>         A
            \         /
              \     /
                \ /
                 B 

Это специальное правило сокрытия (также называемое правилом доминирования) работает, только если все Alignment<8> объекты одинаковы. Таким образом, мы всегда наследуем виртуально: в этом случае есть только один Alignment<8> (или 16, ...) объект, существующий в любой данной иерархии классов.

mixins - правильный подход, однако перегрузка оператора new - нет. Это выполнит то, что вам нужно:

__declspec(align(256))  struct cachealign{};
__declspec(align(4096)) struct pagealign{};
struct DefaultAlign{};
struct CacheAlign:private cachealign{};
struct PageAlign: CacheAlign,private pagealign{};

void foo(){
 DefaultAlign d;
 CacheAlign c;
 PageAlign p;
 std::cout<<"Alignment of d "<<__alignof(d)<<std::endl;
 std::cout<<"Alignment of c "<<__alignof(c)<<std::endl;
 std::cout<<"Alignment of p "<<__alignof(p)<<std::endl;
}

Печать

Alignment of d 1
Alignment of c 256
Alignment of p 4096

Для gcc используйте

struct cachealign{}__attribute__ ((aligned (256)));

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

При использовании Visual Studio Express 2010 приведенный выше пример не работает с новым:

CacheAlign *c = new CacheAlign;

выдаст __align_of(c) == 4 (я полагаю, что ожидается), но адрес первого члена CacheAlign также не выровнен в соответствии с запросом.

Не недавний вопрос, но если я правильно понимаю OP, у него есть классы, которые являются дочерними по отношению к родительскому классу, которые определяют allocator & deallocator, и все они могут требовать определенного выравнивания. Что плохого в том, чтобы иметь простой new, который вызывает какой-то частный распределитель, который выполняет фактическую работу и получает аргумент выравнивания - универсальную версию с выравниванием по умолчанию в родительском классе, которая наследуется или перегружается версией, которая задает правильное выравнивание?

Другие вопросы по тегам