Контейнеры 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<>
, который обрабатывает распределение динамической памяти, не производит выровненных распределений.