Есть ли какая-либо гарантия выравнивания адреса, возвращаемого новой операцией C++?
Большинство опытных программистов знают, что выравнивание данных важно для производительности программы. Я видел программиста, написавшего программу, которая выделяет больший размер буфера, чем им нужно, и использует выровненный указатель как начало. Мне интересно, должен ли я сделать это в своей программе, я понятия не имею, есть ли какая-либо гарантия выравнивания адреса, возвращаемого новой операцией C++. Поэтому я написал небольшую программу для тестирования
for(size_t i = 0; i < 100; ++i) {
char *p = new char[123];
if(reinterpret_cast<size_t>(p) % 4) {
cout << "*";
system("pause");
}
cout << reinterpret_cast<void *>(p) << endl;
}
for(size_t i = 0; i < 100; ++i) {
short *p = new short[123];
if(reinterpret_cast<size_t>(p) % 4) {
cout << "*";
system("pause");
}
cout << reinterpret_cast<void *>(p) << endl;
}
for(size_t i = 0; i < 100; ++i) {
float *p = new float[123];
if(reinterpret_cast<size_t>(p) % 4) {
cout << "*";
system("pause");
}
cout << reinterpret_cast<void *>(p) << endl;
}
system("pause");
Я использую компилятор Visual C++ Express 2008. Кажется, что все адреса, возвращаемые новой операцией, выровнены. Но я не уверен. Итак, мой вопрос: есть ли гарантия? Если у них есть гарантия, я не должен настраиваться, если нет, я должен.
5 ответов
Выравнивание имеет следующую гарантию от стандарта (3.7.3.1/2):
Возвращаемый указатель должен быть соответствующим образом выровнен, чтобы его можно было преобразовать в указатель любого завершенного типа объекта и затем использовать для доступа к объекту или массиву в выделенном хранилище (до тех пор, пока хранилище не будет явно освобождено путем вызова соответствующей функции освобождения),
РЕДАКТИРОВАТЬ: Спасибо timday за выделение ошибки в gcc/glibc, где гарантия не действует.
РЕДАКТИРОВАТЬ 2: комментарий Бена выдвигает на первый план интересный крайний случай. Требования к процедурам распределения относятся только к тем, которые предусмотрены стандартом. Если у приложения есть собственная версия, такой гарантии на результат нет.
Это поздний ответ, но только для пояснения ситуации в Linux - в 64-битных системах память всегда выровнена по 16 байтов:
http://www.gnu.org/software/libc/manual/html_node/Aligned-Memory-Blocks.html
Адрес блока, возвращаемого функцией malloc или realloc в системе GNU, всегда кратен восьми (или шестнадцати в 64-битных системах).
new
звонки оператора malloc
внутренне (см. ./gcc/libstdc++-v3/libsupc++/new_op.cc
) так что это относится к new
также.
Реализация malloc
которая является частью glibc
в основном определяетMALLOC_ALIGNMENT
быть 2*sizeof(size_t)
а также size_t
32 бит =4 байта и 64 бит =8 байтов в системах x86-32 и x86-64 соответственно.
$ cat ./glibc-2.14/malloc/malloc.c:
...
#ifndef INTERNAL_SIZE_T
#define INTERNAL_SIZE_T size_t
#endif
...
#define SIZE_SZ (sizeof(INTERNAL_SIZE_T))
...
#ifndef MALLOC_ALIGNMENT
#define MALLOC_ALIGNMENT (2 * SIZE_SZ)
#endif
C++17 меняет требования к new
распределитель, такой, что требуется вернуть указатель, выравнивание которого равно макросу __STDCPP_DEFAULT_NEW_ALIGNMENT__
(который определяется реализацией, а не включением заголовка).
Это важно, потому что этот размер может быть больше, чем alignof(std::max_align_t)
, Например, в Visual C++ максимальное регулярное выравнивание составляет 8 байт, но по умолчанию new
всегда возвращает 16-байтовую выровненную память.
Также обратите внимание, что если вы переопределите значение по умолчанию new
с вашим собственным распределителем, вы должны соблюдать __STDCPP_DEFAULT_NEW_ALIGNMENT__
также.
Кстати, в документации MS упоминается кое-что о возвращаемых адресах malloc/new, которые выровнены по 16 байтов, но из экспериментов это не так. Мне понадобилось 16-байтовое выравнивание для проекта (чтобы ускорить копирование памяти с расширенным набором инструкций), в конце концов я прибег к написанию собственного распределителя...
Оператор new/new[] платформы будет возвращать указатели с достаточным выравниванием, чтобы он хорошо работал с основными типами данных (double, float и т. По крайней мере, любой разумный компилятор C++ + среда выполнения должны это делать.
Если у вас есть особые требования к выравниванию, такие как для SSE, то, вероятно, неплохо использовать специальные функции align_malloc или свернуть свои собственные.
Я работал над системой, где они использовали выравнивание, чтобы освободить лишний бит для собственного использования!
Они использовали нечетный бит для реализации системы виртуальной памяти.
Когда для указателя был установлен нечетный бит, они использовали его для обозначения того, что он указывает (за вычетом нечетного бита) на информацию для получения данных из базы данных, а не на сами данные.
Я думал, что это особенно неприятный код, который был далеко не умен для своего блага!!
Тони