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

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

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

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

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

34
задан Michael Myers 2 March 2010 в 21:46
поделиться

2 ответа

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

#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 наследует DeAllocator . Ответ заключается в том, что в данной иерархии могут быть разные требования к выравниванию, налагаемые классами. Представьте, что IBase не имеет требований к выравниванию, A требует 8 байтов, а B требует 16 байтов и наследует A :

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

Выравнивание <16> и Выравнивание <8> оба предоставляют оператор 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 наследовать их виртуально, то оператор new в Выравнивание <8> будет скрыто:

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

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

21
ответ дан 27 November 2019 в 17:14
поделиться

миксины - правильный подход, однако перегрузка оператора 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;
}

Prints

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

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

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

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

7
ответ дан 27 November 2019 в 17:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: