предотвращение невыровненных данных в куче - PullRequest
9 голосов
/ 22 июня 2010

Я строю иерархию классов, в которой используются встроенные функции SSE, и поэтому некоторые члены класса должны быть выровнены по 16 байтов. Для экземпляров стека я могу использовать __declspec(align(#)), например:

typedef __declspec(align(16)) float Vector[4];
class MyClass{
...
private:
Vector v;
};

Теперь, поскольку __declspec(align(#)) является директивой компиляции, следующий код может привести к невыровненному экземпляру Vector в куче:

MyClass *myclass = new MyClass;

Это также, я знаю, я могу легко решить, перегрузив новые и delete операторы для использования _aligned_malloc и _aligned_free соответственно. Вот так:

//inside MyClass:
public:
void* operator new (size_t size) throw (std::bad_alloc){
    void * p = _aligned_malloc(size, 16);
    if (p == 0)  throw std::bad_alloc()
    return p; 
}

void operator delete (void *p){
    MyClass* pc = static_cast<MyClass*>(p); 
    _aligned_free(p);
}
...

Пока все хорошо ... но вот моя проблема. Рассмотрим следующий код:

class NotMyClass{ //Not my code, which I have little or no influence over
...
MyClass myclass;
...
};
int main(){
    ...
    NotMyClass *nmc = new NotMyClass;
    ...
}

Поскольку экземпляр myclass MyClass создается статически на динамическом экземпляре NotMyClass, myclass будет выровнен на 16 байт относительно начала nmc из-за директивы Vector __declspec(align(16)). Но это бесполезно, поскольку nmc динамически выделяется в куче с помощью оператора new NotMyClass, который не обязательно (и, вероятно, вероятно, НЕ) 16-байтовое выравнивание.

Пока что я могу думать только о двух подходах к решению этой проблемы:

  1. Запрет пользователям MyClass компилировать следующий код:

    MyClass myclass;
    

    Это означает, что экземпляры MyClass могут создаваться только динамически, с использованием оператора new, таким образом гарантируя, что все экземпляры MyClass действительно динамически распределяются с перегруженным новым MyClass. Я проконсультировался в другой ветке о том, как это сделать, и получил несколько отличных ответов: C ++, предотвращение создания экземпляра класса в стеке (во время компиляции)

  2. Отказаться от использования элементов Vector в моем классе и указателей на Vector только в качестве членов, которые я буду размещать и отменять, используя _aligned_malloc и _aligned_free в ctor и dtor соответственно. Этот метод кажется грубым и подверженным ошибкам, поскольку я не единственный программист, пишущий эти классы (MyClass является производным от базового класса, и многие из этих классов используют SSE).

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

Ответы [ 3 ]

4 голосов
/ 23 июня 2010

Если вы настроены на выделение кучи, другой идеей является избыточное выделение в стеке и ручное выравнивание (ручное выравнивание обсуждается в этой публикации SO ).Идея состоит в том, чтобы выделить байтовые данные (unsigned char) с размером, гарантированно содержащим выровненную область необходимого размера (+15), а затем найти выровненную позицию путем округления вниз от наиболее смещенной области (x+15 - (x+15) % 16,или x+15 & ~0x0F).Я опубликовал рабочий пример этого подхода с векторными операциями на codepad (для g++ -O2 -msse2).Вот важные биты:

class MyClass{
   ...
   unsigned char dPtr[sizeof(float)*4+15]; //over-allocated data
   float* vPtr;                            //float ptr to be aligned

   public:
      MyClass(void) : 
         vPtr( reinterpret_cast<float*>( 
            (reinterpret_cast<uintptr_t>(dPtr)+15) & ~ 0x0F
         ) ) 
      {}
   ...
};
...

Конструктор обеспечивает выравнивание vPtr (обратите внимание, что порядок членов в объявлении класса важен).

Этот подход работает (распределение кучи / стека содержащих классов не имеет отношения к выравниванию), является портируемым (я думаю, что большинство компиляторов предоставляют указатель размера uint uintptr_t) и не будут пропускать память.Но это не особенно безопасно (быть уверенным, что выровненный указатель действителен при копировании и т. Д.), Тратит (почти) столько памяти, сколько использует, а некоторые могут счесть reinterpret_casts неприятным.

Риски проблем с выровненными операциями / не выровненными данными можно было бы в основном устранить, инкапсулировав эту логику в объект Vector, тем самым контролируя доступ к выровненному указателю и гарантируя, что он выравнивается при построении и остается действительным.

1 голос
/ 23 июня 2010

Вы можете использовать «размещение нового».

void* operator new(size_t, void* p) { return p; }

int main() {
    void* p = aligned_alloc(sizeof(NotMyClass));
    NotMyClass* nmc = new (p) NotMyClass;
    // ...

    nmc->~NotMyClass();
    aligned_free(p);
}

Конечно, вы должны быть осторожны при уничтожении объекта, вызывая деструктор и затем освобождая пространство.Вы не можете просто позвонить удалить.Вы можете использовать shared_ptr <> с другой функцией для автоматического решения этой проблемы;это зависит от того, являются ли для вас проблемой накладные расходы на shared_ptr (или другую оболочку указателя).

0 голосов
/ 23 июня 2010

Предстоящий стандарт C ++ 0x предлагает средства для работы с необработанной памятью. Они уже включены в VC ++ 2010 (в пределах пространства имен tr1).

std::tr1::alignment_of // get the alignment
std::tr1::aligned_storage // get aligned storage of required dimension

Это типы, вы можете использовать их так:

static const floatalign = std::tr1::alignment_of<float>::value; // demo only

typedef std::tr1::aligned_storage<sizeof(float)*4, 16>::type raw_vector;
        // first parameter is size, second is desired alignment

Тогда вы можете объявить свой класс:

class MyClass
{
public:

private:
  raw_vector mVector; // alignment guaranteed
};

Наконец, вам нужно некоторое приведение, чтобы манипулировать им (это сырая память до сих пор):

float* MyClass::AccessVector()
{
  return reinterpret_cast<float*>((void*)&mVector));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...