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

Следующая выдержка отсюда

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

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

также увидеть быстрый pimpl из травяной саттер, он сказал:

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

Мне любопытно, как Malloc узнает выравнивание пользовательского типа?

7 ответов

Решение

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

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

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

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

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

Я думаю, что наиболее важной частью цитаты Херба Саттера является часть, которую я выделил жирным шрифтом:

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

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

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

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

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

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

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

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

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

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

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

Но когда дело доходит до размещения новых, то C++11 приносит нам новые ключевые слова, называемые alignof и 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);
}

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

РЕДАКТИРОВАТЬ: Заменены дорогие вычисления по модулю с побитовыми операциями. Все еще надеемся, что кто-то найдет что-то еще быстрее.

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

Вы можете узнать биты распределения для своей реализации malloc() с помощью этой небольшой программы на C:

      #include <stdlib.h>
#include <stdio.h>

int main()
{
    size_t
        find = 0,
        size;
    for( unsigned i = 1000000; i--; )
        if( size = rand() & 127 )
            find |= (size_t)malloc( size );
    char bits = 0;
    for( ; !(find & 1); find >>= 1, ++bits );
    printf( "%d", (int)bits );
}
Другие вопросы по тегам