Подтвердить что ты не робот

Выравнивание памяти?

У меня есть несколько связанных вопросов об управлении выровненными блоками памяти. Кросс-платформенные ответы были бы идеальными. Однако, поскольку я уверен, что кросс-платформенное решение не существует, меня в основном интересуют Windows и Linux, а также (в гораздо меньшей степени) Mac OS и FreeBSD.

  • Какой лучший способ получить кусок памяти, выровненный по 16-байтовым границам? (Я знаю о тривиальном методе использования malloc(), выделяя немного дополнительного пространства, а затем набрасывая указатель до правильно выровненного значения. Я надеюсь на что-то немного меньше kludge-y. ниже для дополнительных вопросов.)

  • Если я использую простой старый malloc(), выделите дополнительное пространство, а затем переместите указатель вверх туда, где он будет правильно выровнен, нужно ли держать указатель на начало блока вокруг для освобождения? (Вызов free() по указателям на середину блока, похоже, работает на практике в Windows, но мне интересно, что говорит стандарт, и даже если стандарт говорит, что вы не можете, работает ли он на практике по всем основным OS. Я не забочусь о неясных DS9K-подобных ОС.)

  • Это трудная/интересная часть. Какой лучший способ перераспределить блок памяти при сохранении выравнивания? В идеале это было бы более разумным, чем вызов malloc(), копирование, а затем вызов free() на старый блок. Я хотел бы сделать это на месте, где это возможно.

4b9b3361

Ответ 1

  • Если ваша реализация имеет стандартный тип данных, для которого требуется выравнивание по 16 байт (например, long long), malloc уже гарантирует правильное выравнивание возвращаемых блоков. Раздел 7.20.3 состояний C99 The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object.

  • Вы должны передать тот же самый адрес в free, как вам дали malloc. Без исключений. Так что да, вам нужно сохранить оригинальную копию.

  • См. выше (1), если у вас уже есть требуемый 16-байтовый тип соответствия.

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

Я сам реализую malloc16 слой поверх malloc, который использовал бы следующую структуру:

some padding for alignment (0-15 bytes)
size of padding (1 byte)
16-byte-aligned area

Затем вызовите функцию malloc16() malloc, чтобы получить блок размером в 16 байт, чтобы выяснить, где расположена выровненная область, поместите длину заполнения до этого и верните адрес выравниваемой области.

Для free16 вы просто посмотрите на байт перед указанным адресом, чтобы получить длину заполнения, выработать фактический адрес блока malloc'ed и передать это на free.

Это непроверено, но должно быть хорошим началом:

void *malloc16 (size_t s) {
    unsigned char *p;
    unsigned char *porig = malloc (s + 0x10);   // allocate extra
    if (porig == NULL) return NULL;             // catch out of memory
    p = (porig + 16) & (~0xf);                  // insert padding
    *(p-1) = p - porig;                         // store padding size
    return p;
}

void free16(void *p) {
    unsigned char *porig = p;                   // work out original
    porig = porig - *(porig-1);                 // by subtracting padding
    free (porig);                               // then free that
}

Волшебная линия в malloc16 равна p = (porig + 16) & (~0xf);, которая добавляет 16 к адресу, а затем устанавливает нижние 4 бита в 0, фактически возвращает его в следующую нижнюю точку выравнивания (+16 гарантирует, что это прошло фактическое начало блока maloc'ed).

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

Ответ 2

  • Я не знаю, как запросить возврат памяти malloc с более строгим выравниванием, чем обычно. Что касается "обычного" в Linux, от человека posix_memalign (который вы можете использовать вместо malloc(), чтобы получить более строго выровненную память, если хотите):

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

  • Вы должны освободить() память, используя тот же указатель, возвращаемый malloc(), posix_memalign() или realloc().

  • Используйте realloc(), как обычно, включая достаточное дополнительное пространство, поэтому, если возвращается новый адрес, который еще не выровнен, вы можете memmove() его немного, чтобы выровнять его. Нест, но лучше всего я могу думать.

Ответ 3

Вы можете написать свой собственный slab allocator для обработки ваших объектов, он может выделять страницы одновременно с помощью mmap, поддерживать кеш недавно освобожденные адреса для быстрого распределения, обрабатывают все ваши настройки для вас и дают вам гибкость для перемещения/создания объектов точно так, как вам нужно. malloc неплохо подходит для распределений общего назначения, но если вы знаете свои макеты данных и потребности в распределении, вы можете создать систему, которая точно соответствует этим требованиям.

Ответ 4

Самое сложное требование, очевидно, является третьим, поскольку любое решение на основе malloc()/realloc() является заложником realloc(), перемещая блок в другое выравнивание.

В Linux вы можете использовать анонимные сопоставления, созданные с помощью mmap() вместо malloc(). Адреса, возвращаемые mmap(), по необходимости выравниваются по страницам, а отображение может быть расширено с помощью mremap().

Ответ 5

Запустив C11, у вас есть примитивы void *aligned_alloc( size_t alignment, size_t size );, где параметры:

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

Возвращаемое значение

При успешном завершении возвращает указатель на начало новой выделенной памяти. Возвращенный указатель должен быть освобожден с помощью free() или realloc().

В случае сбоя возвращается нулевой указатель.

Пример:

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


    int main(void)
    {
        int *p1 = malloc(10*sizeof *p1);
        printf("default-aligned addr:   %p\n", (void*)p1);
        free(p1);

        int *p2 = aligned_alloc(1024, 1024*sizeof *p2);
        printf("1024-byte aligned addr: %p\n", (void*)p2);
        free(p2);
    }

Возможный выход:

default-aligned addr:   0x1e40c20
1024-byte aligned addr: 0x1e41000

Ответ 6

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

    Например, 64-разрядная версия Linux на x86/64 имеет 16-байтный long double, который выравнивается по 16 байт, поэтому все распределения памяти выравниваются по 16 байт. Однако с 32-разрядной программой sizeof(long double) равно 8, а выделение памяти выполняется только по 8 байт.

  • Да - вы можете только free() вернуть указатель на malloc(). Все остальное - это рецепт катастрофы.

  • Если ваша система выполняет выравнивание по 16 байт, это не проблема. Если это не так, тогда вам понадобится ваш собственный перераспределитель, который выполняет выравнивание по 16 байт, а затем копирует данные - или использует систему realloc() и при необходимости настраивает перестроенные данные.

Дважды проверьте страницу руководства для своего malloc(); могут быть варианты и механизмы, чтобы настроить его так, чтобы он вел себя так, как вы хотите.

В MacOS X есть posix_memalign() и valloc() (который дает выделение с выравниванием по странице), и существует целая серия функций "zoned malloc", идентифицированных man malloc_zoned_malloc, а заголовок <malloc/malloc.h>.

Ответ 7

Возможно, вы сможете использовать jimmy (в Microsoft VС++ и, возможно, другие компиляторы):

#pragma pack(16)

так что malloc() принудительно возвращает указатель с 16-байтовым выравниванием. Что-то вроде:

ptr_16byte = malloc( 10 * sizeof( my_16byte_aligned_struct ));

Если бы он работал вообще для malloc(), я бы подумал, что он будет работать и для realloc().

Просто мысль.

- pete