Мне действительно нужно беспокоиться о выравнивании при использовании нового оператора размещения?
Я прочитал это. Когда я должен беспокоиться о выравнивании? но я все еще не знаю, нужно ли мне беспокоиться о не выровненном указателе, возвращаемом оператором размещения 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
как определено в:
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 имеет опцию, чтобы сообщить компилятору, управляются ли не выровненные обращения системой или нет.
Я бы предположил, что в любом случае доступ к этой платформе будет очень медленным, и его следует избегать, насколько это возможно.