Выровняли управление памятью? - PullRequest
21 голосов
/ 21 февраля 2011

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

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

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

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

Ответы [ 7 ]

19 голосов
/ 21 февраля 2011
  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.

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

  3. См. (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'а и передайте его в 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 гарантирует, что оно прошло фактическое начало выделенного блока).

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

1 голос
/ 01 мая 2017

Начиная с 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
1 голос
/ 21 февраля 2011

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

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

1 голос
/ 21 февраля 2011

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

1 голос
/ 21 февраля 2011
  1. Я не знаю ни одного способа запроса памяти возврата malloc с более строгим выравниванием, чем обычно. Что касается «обычного» в Linux, от man posix_memalign (который вы можете использовать вместо malloc () для получения более строго выровненной памяти, если хотите):

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

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

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

0 голосов
/ 21 февраля 2011

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

#pragma pack(16)

, так что malloc () вынужден вернуть16-байтовый выровненный указатель.Что-то вроде:

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

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

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

- Пит

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

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

  2. Да - вы можете только free() указатель, возвращаемый malloc().Все остальное - путь к катастрофе.

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

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

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

...