Как malloc понимает выравнивание?

после выдержки из здесь

pw = (widget *)malloc(sizeof(widget));

выделяет необработанное хранилище. Действительно, вызов malloc выделяет хранилище достаточно большой и , подходящим для размещения объекта типа виджет

также см. быстрый pImpl от травяного саттера, он сказал:

Alignment. Любое выравнивание памяти. Любая память, выделенная динамически через новый или malloc гарантированно правильно выровнены для объекты любого типа, но буферы, которые не распределяются динамически не имеют такой гарантии

Мне интересно, как malloc знает выравнивание пользовательского типа?

Ответ 1

Требования к выравниванию рекурсивны: выравнивание любой struct - это просто наибольшее выравнивание любого из ее членов, и это понимается рекурсивно.

Например, и предполагая, что каждое выравнивание фундаментального типа равно его размеру (это не всегда верно в общем случае), struct X { int; char; double; } struct X { int; char; double; } struct X { int; char; double; } имеет выравнивание double, и оно будет дополнено кратным размеру double (например, 4 (int), 1 (char), 3 (padding), 8 (double)). Структура struct Y { int; X; float; } struct Y { int; X; float; } struct Y { int; X; float; } имеет выравнивание X, которое является наибольшим и равным выравниванию double, а Y выкладывается соответственно: 4 (int), 4 (заполнение), 16 (X), 4 (плавание), 4 (заполнение),

(Все цифры являются лишь примерами и могут отличаться на вашем компьютере.)

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

Все, что нужно сделать malloc() - это выбрать адрес, кратный этому значению.

Ответ 2

Я думаю, что самая важная часть цитаты Herb Sutter - это часть, которую я выделил жирным шрифтом:

Alignment. Любое выравнивание памяти. Любая память, которая динамически распределяется через new или malloc, гарантированно будет правильно выровнена для объектов любого типа, но буферы, которые не распределены динамически, не имеют такой гарантии

Он не должен знать, какой тип вы имеете в виду, потому что он выравнивается для любого типа. В любой заданной системе существует максимальный размер выравнивания, который когда-либо необходим или значим; например, система с четырьмя байтовыми словами, скорее всего, будет содержать максимум четыре байта.

Это также разъясняется malloc(3) man-страница, в котором говорится, в частности:

Функции malloc() и calloc() возвращают указатель на выделенную память, которая соответствующим образом выровнена для любой переменной.

Ответ 3

Единственная информация, которую может использовать malloc(), - это размер переданного ему запроса. В общем, он может сделать что-то вроде округления пройденного размера до ближайшей большей (или равной) мощности двух и выровнять память на основе этого значения. Вероятно, также будет верхняя граница значения выравнивания, например 8 байтов.

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

Ответ 4

1) Выровняйте с наименьшим общим кратным всех выравниваний. например если ints требует 4 байтового выравнивания, но указатели требуют 8, а затем распределяют все по 8-байтовому выравниванию. Это приводит к выравниванию всех элементов.

2) Используйте аргумент размера, чтобы определить правильное выравнивание. Для небольших размеров вы можете вывести тип, например malloc(1) (при условии, что размеры других типов не равны 1) всегда char. С++ new имеет преимущество безопасного типа и поэтому всегда может принимать решения о выравнивании таким образом.

Ответ 5

До выравнивания С++ 11 было обработано достаточно просто, используя наибольшее выравнивание, где точное значение было неизвестно, и malloc/calloc все еще работают таким образом. Это означает, что распределение malloc правильно выровнено для любого типа.

Неправильное выравнивание может привести к поведению undefined в соответствии со стандартом, но я видел, что компиляторы x86 являются щедрыми и только наказывают с меньшей производительностью.

Обратите внимание, что вы также можете настроить выравнивание с помощью параметров или директив компилятора. (например, пакет pragma для VisualStudio).

Но когда дело доходит до размещения нового, тогда С++ 11 приносит нам новые ключевые слова, называемые align и alignas. Вот какой код который показывает эффект, если максимальное выравнивание компилятора больше 1. Первое размещение нового ниже автоматически хорошо, но не второе.

#include <iostream>
#include <malloc.h>
using namespace std;
int main()
{
        struct A { char c; };
        struct B { int i; char c; };

        unsigned char * buffer = (unsigned char *)malloc(1000000);
        long mp = (long)buffer;

        // First placment new
        long alignofA = alignof(A) - 1;
        cout << "alignment of A: " << std::hex << (alignofA + 1) << endl;
        cout << "placement address before alignment: " << std::hex << mp << endl;
        if (mp&alignofA)
        {
            mp |= alignofA;
            ++mp;
        }
        cout << "placement address after alignment : " << std::hex <<mp << endl;
        A * a = new((unsigned char *)mp)A;
        mp += sizeof(A);

        // Second placment new
        long alignofB = alignof(B) - 1;
        cout << "alignment of B: " <<  std::hex << (alignofB + 1) << endl;
        cout << "placement address before alignment: " << std::hex << mp << endl;
        if (mp&alignofB)
        {
            mp |= alignofB;
            ++mp;
        }
        cout << "placement address after alignment : " << std::hex << mp << endl;
        B * b = new((unsigned char *)mp)B;
        mp += sizeof(B);
}

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

EDIT: Заменено дорогостоящее модульное вычисление с помощью побитовых операций. Все еще надеясь, что кто-то найдет что-то еще быстрее.

Ответ 6

malloc не знает, для чего он выделяет, потому что его параметр - просто общий размер. Он просто выравнивается по выравниванию, которое безопасно для любого объекта.