Есть ли какая-либо гарантия выравнивания адреса, возвращаемого новой операцией 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 или свернуть свои собственные.

Я работал над системой, где они использовали выравнивание, чтобы освободить лишний бит для собственного использования!

Они использовали нечетный бит для реализации системы виртуальной памяти.

Когда для указателя был установлен нечетный бит, они использовали его для обозначения того, что он указывает (за вычетом нечетного бита) на информацию для получения данных из базы данных, а не на сами данные.

Я думал, что это особенно неприятный код, который был далеко не умен для своего блага!!

Тони

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