Должен ли я действительно беспокоиться о выравнивании при использовании нового оператора размещения?
Я читал это Когда мне нужно беспокоиться о выравнивании?, но я все еще не знаю, нужно ли мне беспокоиться о не выровненном указателе, возвращаемом при размещении нового оператора - например в этом примере:
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.x.x.
[ОБНОВЛЕНИЕ] Сразу после публикации этого вопроса я прочитал эту статью: http://virtrev.blogspot.de/2010/09/memory-alignment-theory-and-c-examples.html.
Может быть, единственными недостатками в моем случае были бы штраф за производительность, я имею в виду невысокую стоимость доступа больше, чем выровнен?
Ответы
Ответ 1
Когда вы вызываете новое размещение в буфере:
A *a = new (buf) A;
вы вызываете встроенный void* operator new (std::size_t size, void* ptr) noexcept
, как определено в:
С++ 11
18.6.1.3 Формы размещения [new.delete.placement]
Эти функции зарезервированы, программа на С++ не может определять функции, которые заменяют версии в Стандартная библиотека С++ (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, как и ожидалось.
Ответ 2
Просто потому, что все работает, это не значит, что это действительно так.
С++ - это спецификация, которая определяет, что требуется для работы. Компилятор также может заставить не работать. Что означает "undefined поведение": все может случиться, поэтому ваш код больше не переносится.
С++ не требует, чтобы это работало. Поэтому, если вы передадите свой код компилятору, где это не работает, вы больше не можете обвинять С++; это ваша вина за неправильное использование языка.
Ответ 3
Да, все зависит от архитектуры и, возможно, от флагов оптимизации компилятора.
Все будет нормально работать до тех пор, пока вы не выполните A b = a;
или какой-либо другой случайный доступ, который скомпилируется с помощью movdqa
и ваша программа выйдет из строя.
Ответ 4
некоторые процессоры, безусловно, взорвут это (например, sparc), другим это не понравится, другие будут медленными. С++ предполагает, что вы знаете, что делаете (или нет), как и reinterpret_cast и т.д.
Ответ 5
быстрый google сообщает мне, что gcc on powerpc имеет возможность сообщать компилятору, если система без права доступа не привязана к системе.
Я бы предположил, что в любом случае неприглаженные обращения на этой платформе будут очень медленными, и их следует избегать как можно больше.