Контейнеры SSE и С++

Есть ли очевидная причина, почему следующий код segfaults?

#include <vector>
#include <emmintrin.h>

struct point {
    __m128i v;

  point() {
    v = _mm_setr_epi32(0, 0, 0, 0);
  }
};

int main(int argc, char *argv[])
{
  std::vector<point> a(3);
}

Спасибо

Изменить: я использую g++ 4.5.0 на linux/i686, я, возможно, не знаю, что я здесь делаю, но так как даже следующие segfaults

int main(int argc, char *argv[])
{
  point *p = new point();
}

Я действительно думаю, что это должно быть и проблема выравнивания.

Ответы

Ответ 1

Очевидная вещь, которая могла пойти не так, была бы, если v не была правильно выровнена.

Но он динамически выделяется vector, поэтому он не подвержен проблемам смещения стека.

Однако, как phooji правильно указывает, значение "шаблон" или "prototype" передается конструктору std::vector, который будет скопирован ко всем элементам вектора. Этот параметр std::vector::vector, который будет помещен в стек и может быть смещен.

Некоторые компиляторы имеют прагму для управления выравниванием стека внутри функции (в основном, компилятор отнимает дополнительное пространство, необходимое для правильного выравнивания всех локальных жителей).

В соответствии с документацией Microsoft Visual С++ 2010 автоматически должно настроить 8 байтов стека для SSE типов и сделал это с Visual С++ 2003

Для gcc я не знаю.


В С++ 0x для new point() для возврата неравномерного хранения это серьезное несоответствие. [basic.stc.dynamic.allocation] говорит (формулировка из проекта n3225):

Функция распределения пытается выделить запрошенный объем памяти. Если он будет успешным, он должен возвращает адрес начала блока хранения, длина которого в байтах должна быть не меньше, чем запрошенный размер. Нет ограничений на содержимое выделенного хранилища при возврате из функция распределения. Порядок, смежность и начальное значение хранилища, выделенные последовательными вызовами функция распределения не определена. Возвращаемый указатель должен быть соответствующим образом выровнен так, чтобы он мог быть преобразован к указателю любого полного типа объекта с фундаментальным требованием выравнивания (3.11), а затем используется для доступа к объекту или массиву в выделенном хранилище (пока хранилище не будет явно освобождено вызовом соответствующая функция освобождения).

И [basic.align] говорит:

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

Можете ли вы попробовать новую версию gcc, где это может быть исправлено?

Ответ 2

Конструктор vector, который вы используете, на самом деле определяется следующим образом:

explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );

(см., например, http://www.cplusplus.com/reference/stl/vector/vector/).

Другими словами, элемент один по умолчанию сконструирован (т.е. значение параметра по умолчанию при вызове конструктора), а остальные элементы создаются путем копирования первого. Я предполагаю, что вам нужен конструктор копирования для point, который правильно обрабатывает (не) копирование значений __m128i.

Обновление: Когда я пытаюсь создать свой код с помощью Visual Studio 2010 (версия 10.0.30319.1), я получаю следующую ошибку сборки:

error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned c:\program files\microsoft visual studio 10.0\vc\include\vector 870 1   meh

Это говорит о том, что Бен прав на деньги, поскольку это проблема выравнивания.

Ответ 3

Существует вероятность того, что память, выделенная распределителем по умолчанию в вашей реализации STL компилятора, не выровнена. Это будет зависеть от конкретной платформы и поставщика компилятора.

Обычно распределитель по умолчанию использует оператор new, который обычно не гарантирует выравнивание за пределами размера слова (32-разрядного или 64-разрядного). Чтобы решить проблему, может потребоваться реализовать пользовательский распределитель, который использует _aligned_malloc.

Кроме того, простым исправлением (хотя и не удовлетворительным) было бы присвоение значения локальной переменной __m128i, а затем скопировать эту переменную в структуру с использованием неаудированной инструкции. Пример:

struct point {
    __m128i v;
    point() {
        __m128i temp = _mm_setr_epi32(0, 0, 0, 0);
        _mm_storeu_si128(&v, temp);
    }
};

Ответ 4

Внутренние требования SSE должны быть выровнены по 16 байт в памяти. Когда вы выделяете __m128 в стеке, нет проблем, потому что компилятор автоматически выравнивает их правильно. Распределитель по умолчанию для std::vector<>, который обрабатывает распределение динамической памяти, не производит выровненных распределений.