Мне действительно нужно беспокоиться о выравнивании при использовании нового оператора размещения?

Я прочитал это. Когда я должен беспокоиться о выравнивании? но я все еще не знаю, нужно ли мне беспокоиться о не выровненном указателе, возвращаемом оператором размещения new - как в этом примере:

class A {
public:
   long double a;
   long long b;
   A() : a(1.3), b(1234) {}
};

char buffer[64];

int main() {
   // (buffer + 1) used intentionally to have wrong alignment
   A* a = new (buffer + 1) A(); 
   a->~A();
}

__alignof(A) == 4, (buffer + 1) не выровнен по 4, Но все работает отлично - полный пример здесь: http://ideone.com/jBrk8

Если это зависит от архитектуры, то я использую: linux/powerpc/g++ 4.xx

[ОБНОВЛЕНИЕ] Сразу после публикации этого вопроса я прочитал эту статью: http://virtrev.blogspot.de/2010/09/memory-alignment-theory-and-c-examples.html. Может быть, единственным недостатком в моем случае было бы снижение производительности, я имею в виду стоимость выравниваемого доступа больше, чем выравнивание?

5 ответов

Решение

Когда вы называете размещение новым в буфере:

A *a = new (buf) A;

вы вызываете встроенный void* operator new (std::size_t size, void* ptr) noexcept как определено в:

C++ 11

18.6.1.3 Формы размещения [new.delete.placement]

Эти функции зарезервированы, программа на C++ может не определять функции, которые заменяют версии в стандартной библиотеке C++ (17.6.4). Положения (3.7.4) не применяются к этим формам зарезервированного размещения оператора new и оператора delete.

void* operator new(std::size_t size, void* ptr) noexcept;
Возвращает: ptr,
Примечания: Преднамеренно не выполняет никаких других действий.

Положения (3.7.4) включают в себя, что возвращаемый указатель должен быть соответствующим образом выровнен, так что это хорошо для void* operator new (std::size_t size, void* ptr) noexcept чтобы вернуть не выровненный указатель, если он был передан. Это, однако, не позволяет вам сорваться с крючка:

5.3.4 Новый [expr.new]

[14] Примечание: когда функция выделения возвращает значение, отличное от нуля, это должен быть указатель на блок хранения, в котором зарезервировано пространство для объекта. Предполагается, что блок хранения выровнен соответствующим образом и имеет запрошенный размер.

Поэтому, если вы передаете невыровненное хранилище выражению с новым размещением, вы нарушаете предположение, что хранилище выровнено, и в результате получается UB.


Действительно, в вашей программе выше, если вы замените long long b с __m128 b (после #include <xmmintrin.h>) тогда программа будет segfault, как и ожидалось.

То, что все работает, не значит, что оно действительно работает.

C++ - это спецификация, которая определяет, что требуется для работы. Компилятор также может заставить работать ненужные вещи. Вот что означает "неопределенное поведение": все может произойти, поэтому ваш код больше не переносим.

C++ не требует, чтобы это работало. Так что, если вы берете свой код в компилятор, где это не работает, вы больше не можете винить C++; это ваша вина за неправильное использование языка.

Некоторые процессоры определенно взорвутся этим (например, sparc), другим будет все равно, другие будут работать медленно. C++ предполагает, что вы знаете, что делаете (или нет), так же, как reinterpret_cast и т. Д.

Да, все зависит от архитектуры и, возможно, от флагов оптимизации компилятора. Все будет работать нормально, пока вы не сделаете A b = a; или какой-то другой произвольный доступ, который компилируется в некоторые movdqa ops и ваша программа вылетает.

Быстрый google говорит мне, что gcc на powerpc имеет опцию, чтобы сообщить компилятору, управляются ли не выровненные обращения системой или нет.

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

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