Как выделить выровненную память только с использованием стандартной библиотеки?

341

Я только что закончил тест, как часть собеседования, и один вопрос перепутал меня - даже с помощью Google для справки. Я хотел бы посмотреть, что может сделать команда stackoverflow:

Для функции "memset_16aligned" требуется 16-байтовый выровненный указатель, переданный ему, или он сработает.

a) Как бы вы выделили 1024 байта памяти и выровняли ее с 16-байтовой границей?
б) освободите память после выполнения функции memset_16aligned.

{

   void *mem;

   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here

}
  • 83
    хммм ... для долгосрочной жизнеспособности кода, как насчет "Запустите того, кто написал memset_16aligned, исправьте или замените его так, чтобы у него не было особого граничного условия"
  • 26
    Безусловно актуальный вопрос, который нужно задать - «почему своеобразное выравнивание памяти». Но для этого могут быть веские причины - в этом случае может быть, что memset_16aligned () может использовать 128-битные целые числа, и это проще, если известно, что память выровнена. И т.п.
Показать ещё 6 комментариев
Теги:
memory-management

17 ответов

548
Лучший ответ

Оригинальный ответ

{
    void *mem = malloc(1024+16);
    void *ptr = ((char *)mem+16) & ~ 0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

Исправлен ответ

{
    void *mem = malloc(1024+15);
    void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

Объяснение по запросу

Первым шагом является выделение достаточного количества свободного места на всякий случай. Поскольку память должна быть выровнена на 16 байт (это означает, что старший адрес байта должен быть кратным 16), добавление 16 дополнительных байтов гарантирует, что у нас достаточно места. Где-то в первых 16 байтах есть 16-байтовый выровненный указатель. (Обратите внимание, что malloc() должен возвращать указатель, который достаточно хорошо выровнен для любых целей. Однако значение "any" в основном относится к вещам типа базовых типов - long, double, long double, long long и указатели на объекты и указатели на функции. Когда вы делаете более специализированные вещи, например, играя с графическими системами, они могут нуждаться в более строгом выравнивании, чем в остальной части системы, - следовательно, вопросы и ответы вроде этого.)

Следующим шагом будет преобразование указателя void в указатель char; GCC, тем не менее, вы не должны выполнять арифметику указателей на указатели void (и GCC имеет предупреждения, чтобы сообщить вам, когда вы злоупотребляете им). Затем добавьте 16 к указателю начала. Предположим, что malloc() вернул вам невероятно плохо выровненный указатель: 0x800001. Добавление 16 дает 0x800011. Теперь я хочу округлить до 16-байтной границы - так что я хочу reset последние 4 бита до 0. 0x0F имеет последние 4 бита, установленные на единицу; поэтому ~0x0F имеет все биты, установленные на единицу, за исключением последних четырех. Anding, что с 0x800011 дает 0x800010. Вы можете перебирать другие смещения и видеть, что работает одна и та же арифметика.

Последний шаг, free(), прост: вы всегда и только возвращаете к free() значение, возвращаемое вам одним из malloc(), calloc() или realloc() - все остальное - катастрофа, Вы правильно предоставили mem, чтобы сохранить это значение - спасибо. Это освобождает его.

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

Кто-то упомянул posix_memalign() как еще один способ получить выровненную память; которые недоступны повсюду, но часто могут быть реализованы с использованием этого в качестве основы. Обратите внимание, что было удобно, чтобы выравнивание составляло 2; другие выравнивания более беспорядочны.

Еще один комментарий - этот код не проверяет, что выделение выполнено успешно.

Изменение

Программист Windows указал, что вы не можете выполнять операции с битовой маской на указателях, и, действительно, GCC (тесты 3.4.6 и 4.3.1) жалуются как это. Итак, следует изменить измененный вариант базового кода, преобразованный в основную программу. Я также взял на себя смелость добавить только 15 вместо 16, как было указано. Я использую uintptr_t, поскольку C99 существует достаточно долго, чтобы быть доступным на большинстве платформ. Если бы не было PRIXPTR в операторах printf(), для #include <stdint.h> было бы достаточно #include <stdint.h> вместо использования #include <inttypes.h>. [Этот код включает исправление, указанное CR, в котором повторился пункт, впервые сделанный Bill K a число лет назад, которое мне удалось упустить до сих пор.]

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

int main(void)
{
    void *mem = malloc(1024+15);
    void *ptr = (void *)(((uintptr_t)mem+15) & ~ (uintptr_t)0x0F);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
    return(0);
}

И вот немного более обобщенная версия, которая будет работать для размеров, которые имеют мощность 2:

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

static void test_mask(size_t align)
{
    uintptr_t mask = ~(uintptr_t)(align - 1);
    void *mem = malloc(1024+align-1);
    void *ptr = (void *)(((uintptr_t)mem+align-1) & mask);
    assert((align & (align - 1)) == 0);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

int main(void)
{
    test_mask(16);
    test_mask(32);
    test_mask(64);
    test_mask(128);
    return(0);
}

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

Проблемы с интервьюерами

Uri прокомментировал: Возможно, сегодня утром у меня проблема с пониманием прочитанного, но если в вопросе интервью конкретно говорится: "Как бы вы выделили 1024 байта памяти", и вы явно выделяете больше, чем это. Не будет ли это автоматическим провалом у интервьюера?

Мой ответ не помещается в комментарий из 300 символов...

Это зависит, я полагаю. Я думаю, что большинство людей (включая меня) задали вопрос: "Как бы вы выделили пространство, в котором можно хранить 1024 байта данных, а базовый адрес - кратно 16 байтам". Если интервьюер действительно имел в виду, как вы можете выделить 1024 байта (только) и выровнять по 16 байт, то опции более ограничены.

  • Очевидно, что одна возможность состоит в том, чтобы выделить 1024 байта, а затем дать этому адресу "выравнивание"; проблема с этим подходом заключается в том, что фактическое доступное пространство не определено должным образом (полезное пространство находится между 1008 и 1024 байтами, но не было механизма для определения того, какой размер), что делает его менее полезным.
  • Другая возможность заключается в том, что вы должны написать полный распределитель памяти и убедиться, что 1024-байтовый блок, который вы возвращаете, соответствующим образом выровнен. Если это так, вы, вероятно, в конечном итоге выполняете операцию, довольно похожую на то, что предлагалось, но вы скрываете ее внутри распределителя.

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

Мир перемещается на

Название вопроса изменилось недавно. Это решало совпадение памяти в вопросе интервью C, которое меня тошнило. Пересмотренный заголовок (Как выделить выровненную память только с использованием стандартной библиотеки?) Требует слегка пересмотренного ответа - это добавление предоставляет его.

C11 (ISO/IEC 9899: 2011) добавлена ​​функция aligned_alloc():

7.22.3.1 Функция aligned_alloc

Сводка

#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

Описание
Функция aligned_alloc выделяет пространство для объекта, выравнивание которого указанный alignment, размер которого указан size, и значение которого равно неопределенный. Значение alignment должно быть допустимым выравниванием, поддерживаемым реализацией, а значение size должно быть целочисленным кратным alignment.

Возвращает
Функция aligned_alloc возвращает либо нулевой указатель, либо указатель на выделенное пространство.

И POSIX определяет posix_memalign():

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

ОПИСАНИЕ

Функция posix_memalign() должна выделять size байт, выровненный по границе, указанной alignment, и возвращает указатель на выделенную память в memptr. Значение alignment должно быть мощностью двух кратных sizeof(void *).

После успешного завершения значение, на которое указывает memptr, должно быть кратно alignment.

Если размер запрашиваемого пространства равен 0, поведение определяется реализацией; значение, возвращаемое в memptr, должно быть либо нулевым указателем, либо уникальным указателем.

Функция free() должна освобождать память, ранее выделенную posix_memalign().

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

После успешного завершения posix_memalign() возвращает ноль; в противном случае номер ошибки должен быть возвращен для указания ошибки.

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

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

  • 1
    Объяснение сделало бы чудеса для этого ответа
  • 1
    Что делает маска, так это удостоверяется, что младшие четыре бита равны 0, что эквивалентно тому, чтобы сказать, что адрес выровнен по 16 байтам (поскольку адрес, рассматриваемый как целое число, делится на 16).
Показать ещё 41 комментарий
51

Три немного разных ответа зависят от того, как вы смотрите на вопрос:

1) Достаточно хорошо для точного вопроса, заданного решением Джонатана Леффлера, за исключением того, что для округления до 16-кратного вам нужно всего 15 дополнительных байтов, а не 16.

А:

/* allocate a buffer with room to add 0-15 bytes to ensure 16-alignment */
void *mem = malloc(1024+15);
ASSERT(mem); // some kind of error-handling code
/* round up to multiple of 16: add 15 and then round down by masking */
void *ptr = ((char*)mem+15) & ~ (size_t)0x0F;

В:

free(mem);

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

А:

void *mem = malloc(1024+15+sizeof(void*));
if (!mem) return mem;
void *ptr = ((char*)mem+sizeof(void*)+15) & ~ (size_t)0x0F;
((void**)ptr)[-1] = mem;
return ptr;

В:

if (ptr) free(((void**)ptr)[-1]);

Обратите внимание, что в отличие от (1), где в mem добавлено только 15 байт, этот код может фактически уменьшить выравнивание, если ваша реализация произойдет, чтобы гарантировать 32-байтовое выравнивание от malloc (маловероятно, но теоретически реализация C может иметь 32-байтовый выровненный тип). Это не имеет значения, если все, что вы делаете, это вызов memset_16aligned, но если вы используете память для структуры, это может иметь значение.

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

[Добавлено: "Стандартный" трюк заключается в создании объединения "вероятно, чтобы быть максимально выровненными типами" для определения необходимого выравнивания. Максимально выровненные типы, вероятно, будут (в C99) 'long long', 'long double', 'void *' или 'void (*)(void)'; если вы включили <stdint.h>, вы могли бы использовать 'intmax_t' вместо long long (а на машинах Power 6 (AIX) intmax_t предоставит вам 128-битный целочисленный тип). Требования к выравниванию для этого объединения можно определить, вставив его в структуру с одним char, за которым следует объединение:

struct alignment
{
    char     c;
    union
    {
        intmax_t      imax;
        long double   ldbl;
        void         *vptr;
        void        (*fptr)(void);
    }        u;
} align_data;
size_t align = (char *)&align_data.u.imax - &align_data.c;

Затем вы использовали бы большее значение запрошенного выравнивания (в примере, 16) и значение align, вычисленное выше.

Вкл (64-разрядная) Solaris 10, похоже, что базовое выравнивание для результата из malloc() является кратным 32 байтам.
]

На практике выровненные распределители часто принимают параметр для выравнивания, а не жестко привязанный. Таким образом, пользователь будет передавать размер структуры, которой они заботятся (или наименьшая мощность 2 больше или равна таковой), и все будет хорошо.

3) Используйте то, что предлагает ваша платформа: posix_memalign для POSIX, _aligned_malloc в Windows.

4) Если вы используете C11, то самая чистая - портативная и лаконичная опция - использовать стандартную библиотечную функцию aligned_alloc, которая была представленный в этой версии спецификации языка.

  • 1
    Я согласен - я думаю, что цель вопроса в том, чтобы код, который освобождает блок памяти, имел доступ только к «приготовленному» 16-байтовому выровненному указателю.
  • 1
    Для общего решения - вы правы. Тем не менее, шаблон кода в вопросе ясно показывает оба.
Показать ещё 4 комментария
36

Вы также можете попробовать posix_memalign() (на платформах POSIX, конечно).

  • 13
    И _aligned_malloc в Windows.
  • 9
    К этому следует добавить несколько лет спустя, функция «align_alloc» теперь является частью спецификации C11: open-std.org/jtc1/sc22/wg14/www/docs/n1516.pdf (стр. 346).
19

Здесь альтернативный подход к части "round up". Не самое блестяще закодированное решение, но оно выполняет свою работу, и этот тип синтаксиса немного легче запомнить (плюс будет работать для значений выравнивания, которые не имеют значения 2). Приведение uintptr_t было необходимо, чтобы успокоить компилятор; арифметика указателя не очень любит деление или умножение.

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
  • 2
    В общем, когда у вас есть «unsigned long long», у вас также есть uintptr_t, который явно определен как достаточно большой, чтобы содержать указатель данных (void *). Но ваше решение действительно имеет свои достоинства, если по какой-то причине вам требуется выравнивание, которое не было степенью 2. Маловероятно, но возможно.
  • 0
    @Andrew: Upvoted для этого типа синтаксиса немного легче запомнить (плюс будет работать для значений выравнивания, которые не имеют степени 2) .
18

К сожалению, на C99 кажется довольно сложным гарантировать выравнивание любого типа таким образом, который был бы переносимым в любой реализации C, соответствующей C99. Зачем? Поскольку указатель не может быть "байтовым адресом", который можно представить с помощью модели с плоской памятью. Также не гарантируется представление uintptr_t, которое в любом случае является необязательным типом.

Мы могли бы знать о некоторых реализациях, которые используют представление для void * (и по определению также char *), который является простым байтовым адресом, но C99 это непрозрачно для нас, программистов. Реализация может представлять собой указатель с помощью набора {segment, offset}, где смещение может иметь то, кто знает, что выравнивание "в действительности". Почему, указатель может даже быть какой-то формой хеш-таблицы, или даже ссылочным значением связанного списка. Он может кодировать информацию о границах.

В недавнем проекте C1X для стандарта C мы видим ключевое слово _Alignas. Это может немного помочь.

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

Было бы неплохо ошибиться в этом заявлении.

  • 0
    C11 имеет aligned_alloc() . (C ++ 11/14 / 1z все еще не имеет его). _Alignas() и C ++ alignas() ничего не делают для динамического размещения, только для автоматического и статического хранения (или структурирования).
14

На фронте прокрутки 16 против 15 байтов фактическое число, которое нужно добавить для получения выравнивания по N, составляет max (0, NM), где M - естественное выравнивание памяти распределитель (и оба имеют степень 2).

Поскольку минимальное выравнивание памяти любого распределителя составляет 1 байт, 15 = max (0,16-1) является консервативным ответом. Однако, если вы знаете, что ваш распределитель памяти будет давать вам 32-битные int-align-адреса (что довольно часто), вы могли бы использовать 12 в качестве пэда.

Это не важно для этого примера, но это может быть важно во встроенной системе с 12 КБ ОЗУ, в которой каждый сэкономленный счет подсчитывается.

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

В приведенном ниже примере в большинстве систем значение 1 отлично подходит для MEMORY_ALLOCATOR_NATIVE_ALIGNMENT, однако для нашей теоретической встроенной системы с 32-разрядными выровненными выделениями следующее могло бы сохранить маленький бит драгоценной памяти:

#define MEMORY_ALLOCATOR_NATIVE_ALIGNMENT    4
#define ALIGN_PAD2(N,M) (((N)>(M)) ? ((N)-(M)) : 0)
#define ALIGN_PAD(N) ALIGN_PAD2((N), MEMORY_ALLOCATOR_NATIVE_ALIGNMENT)
8

Возможно, они были бы удовлетворены знанием memalign? И, как указывает Джонатан Леффлер, есть две новые предпочтительные функции, о которых можно узнать.

Упс, Флорин избил меня. Однако, если вы прочитаете справочную страницу, с которой я связан, вы, скорее всего, поймете пример, предоставленный более ранним плакатом.

  • 1
    Обратите внимание, что текущая (на февраль 2016 г.) версия упомянутой страницы гласит: «Функция memalign устарела, и aligned_alloc posix_memalign следует использовать aligned_alloc или posix_memalign ». Я не знаю, что было сказано в октябре 2008 года, но, вероятно, в нем не упоминается aligned_alloc() поскольку это было добавлено в C11.
5

Мы все время делаем для Accelerate.framework, сильно векторизованной библиотеки OS X/iOS, где мы должны постоянно обращать внимание на выравнивание. Существует несколько вариантов, один или два из которых я не видел выше.

Самый быстрый способ для небольшого массива, подобного этому, просто вставить его в стек. С GCC/clang:

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

Нет бесплатной(). Обычно это две инструкции: вычесть 1024 из указателя стека, а затем указатель стека с -ограничением. Предположительно, запросчику нужны данные о куче, потому что его продолжительность жизни в массиве превысила стек, или рекурсия работает, или пространство стека имеет серьезную премию.

В OS X/iOS все вызовы malloc/calloc/etc. всегда выравниваются по 16 байт. Если вам понадобилось 32 байт, выровненных для AVX, например, вы можете использовать posix_memalign:

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

Некоторые люди упомянули интерфейс С++, который работает аналогично.

Не следует забывать, что страницы выровнены с большими степенями по два, поэтому выравниваемые по страницам буферы также выравниваются по 16 байт. Таким образом, mmap() и valloc() и другие аналогичные интерфейсы также являются опциями. mmap() имеет то преимущество, что буфер может быть выделен при инициализации чем-то ненулевым, если хотите. Поскольку они имеют размер с выравниванием по страницам, вы не получите минимальное выделение из них, и при первом прикосновении к нему, вероятно, будет применена ошибка VM.

Cheesy: Включите охранник malloc или аналогичный. Буферы размером n * 16 байтов, такие как этот, будут выравниваться по n * 16 байт, потому что VM используется для захвата перерасхода, а его границы находятся на границах страницы.

Некоторые функции Accelerate.framework берут временный буфер, предоставленный пользователем, для использования в качестве пространства скреста. Здесь мы должны предположить, что буфер, переданный нам, дико смещен, и пользователь активно пытается избавить нашу жизнь от злости. (Наши тестовые примеры прикрепляют страницу защиты до и после буфера temp, чтобы подчеркнуть злобу.) Здесь мы возвращаем минимальный размер, который нам нужен, чтобы гарантировать, что сегмент с 16 байтами выровнен где-то в нем, а затем вручную выровнять буфер после этого. Этот размер желательно_размер + выравнивание - 1. Итак, в этом случае это 1024 + 16 - 1 = 1039 байт. Затем выровняйте так:

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

Добавление выравнивания-1 перемещает указатель мимо первого выровненного адреса, а затем ANDing с -ограничением (например, 0xfff... ff0 для выравнивания = 16) возвращает его к выровненному адресу.

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

Что касается aligned_memset, это довольно глупо. Вам нужно всего лишь занять до 15 байт, чтобы достичь выровненного адреса, а затем продолжить с выровненными хранилищами после этого с некоторым возможным кодом очистки в конце. Вы можете даже выполнять очистку битов в векторном коде, либо в виде неустановленных магазинов, которые перекрывают выровненную область (обеспечивая длину, по крайней мере, длину вектора), либо используя что-то вроде movmaskdqu. Кто-то просто ленится. Тем не менее, вероятно, разумный вопрос интервью, если интервьюер хочет узнать, удобны ли вы с stdint.h, побитовыми операторами и основными принципами памяти, поэтому надуманный пример можно простить.

5

Я удивлен, что никто не проголосовал Shao ответить, что, как я понимаю, это невозможно сделать то, что задано в стандарте C99, поскольку формальное преобразование указателя в интегральный тип является undefined. (Помимо стандарта, допускающего преобразование uintptr_tvoid*, но стандарт, похоже, не позволяет делать какие-либо манипуляции с значением uintptr_t и затем преобразовывать его обратно.)

  • 0
    Не требуется, чтобы тип uintptr_t существовал или чтобы его биты имели какое-либо отношение к битам в базовом указателе. Если нужно было перераспределить хранилище, сохраните указатель как unsigned char* myptr ; а затем вычислить `mptr + = (16- (uintptr_t) my_ptr) & 0x0F, поведение будет определено во всех реализациях, которые определяют my_ptr, но будет ли выровнен результирующий указатель, будет зависеть от отображения между битами uintptr_t и адресами.
3

Первое, что появилось в моей голове при чтении этого вопроса, - это определить выровненную структуру, создать экземпляр, а затем указать на нее.

Есть ли фундаментальная причина, по которой я отсутствую, поскольку никто не предлагал это?

Как sidenote, так как я использовал массив char (предполагая, что система char имеет 8 бит (то есть 1 байт)), я не вижу необходимости в атрибуте ((упакован)) обязательно (исправьте меня, если я ошибаюсь), но я все равно вложил его.

Это работает на двух системах, в которых я это пробовал, но возможно, что есть оптимизация компилятора, которую я не знаю, чтобы дать мне ложные срабатывания по сравнению с эффективностью кода. Я использовал gcc 4.9.2 на OSX и gcc 5.2.1 на Ubuntu.

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

int main ()
{

   void *mem;

   void *ptr;

   // answer a) here
   struct __attribute__((packed)) s_CozyMem {
       char acSpace[16];
   };

   mem = malloc(sizeof(struct s_CozyMem));
   ptr = mem;

   // memset_16aligned(ptr, 0, 1024);

   // Check if it aligned
   if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
   else printf("Rubbish.\n");

   // answer b) here
   free(mem);

   return 1;
}
3

использование memalign, Aligned-Memory-Blocks может быть хорошим решением проблемы.

  • 0
    Обратите внимание, что текущая (на февраль 2016 г.) версия упомянутой страницы гласит: «Функция memalign устарела, и aligned_alloc posix_memalign следует использовать aligned_alloc или posix_memalign ». Я не знаю, что было сказано в октябре 2010 года.
1

Спецификация MacOS X:

  • Все указатели, выделенные malloc, выравниваются по 16 байт.
  • Поддерживается C11, поэтому вы можете просто вызвать aligned_malloc (16, размер).

  • MacOS X выбирает код, оптимизированный для отдельных процессоров во время загрузки для memset, memcpy и memmove, и этот код использует трюки, о которых вы никогда не слышали, чтобы сделать их быстрыми. 99% вероятность того, что memset работает быстрее, чем любая рукописная memset16, которая делает весь вопрос бессмысленным.

Если вам требуется 100% портативное решение, перед C11 его нет. Потому что нет переносного способа проверки выравнивания указателя. Если он не должен быть на 100% переносимым, вы можете использовать

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

Это предполагает, что выравнивание указателя хранится в младших битах при преобразовании указателя в unsigned int. Преобразование в unsigned int теряет информацию и определяется реализацией, но это не имеет значения, потому что мы не преобразуем результат обратно в указатель.

Ужасная часть, конечно же, заключается в том, что исходный указатель должен быть где-то сохранен для вызова free() с ним. Так что в целом я бы действительно сомневался в мудрости этого дизайна.

  • 1
    Где вы находите aligned_malloc в OS X? Я использую Xcode 6.1, и он нигде не определен в iOS SDK, и нигде не объявлен в /usr/include/* .
  • 0
    То же самое для XCode 7.2 на El Capitan (Mac OS X 10.11.3). В любом случае, функция C11 является aligned_alloc() , но она также не объявлена. Из GCC 5.3.0 я получаю интересные сообщения alig.c:7:15: error: incompatible implicit declaration of built-in function 'aligned_alloc' [-Werror] и alig.c:7:15: note: include '<stdlib.h>' or provide a declaration of 'aligned_alloc' . Код действительно включал <stdlib.h> , но ни -std=c11 ни -std=gnu11 изменили сообщения об ошибках.
0

Для решения я использовал концепцию заполнения, которая выравнивает память и не тратит   память одного байта.

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

C11 поддерживается, поэтому вы можете просто вызвать aligned_malloc (16, размер).

void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
  • 0
    Во многих 64-битных системах указатель, возвращаемый malloc() , действительно выровнен по 16-байтовой границе, но ничто в любом стандарте не гарантирует этого - он просто будет достаточно хорошо выровнен для любого использования, и во многих 32-битных системах выравнивания на 8-байтовой границе достаточно, а для некоторых достаточно 4-байтовой границы.
0

Если есть ограничения, которые вы не можете тратить на один байт, то это решение работает: Примечание. Существует случай, когда это может выполняться бесконечно: D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);
  • 0
    Существует очень хороший шанс, что если вы выделите, а затем освободите блок из N байтов, а затем запросите другой блок из N байтов, исходный блок будет возвращен снова. Так что бесконечный цикл очень вероятен, если первое распределение не соответствует требованию выравнивания. Конечно, это позволяет избежать потери одного байта за счет большого количества циклов ЦП.
0

Вы также можете добавить 16 байт, а затем вывести исходный ptr на 16 бит, выровненный, добавив (16-mod), как показано ниже указателя:

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}
0

Просто используйте memalign? http://linux.die.net/man/3/memalign

  • 0
    Обратите внимание, что текущая (на февраль 2016 г.) версия упомянутой страницы описывает «Устаревшая функция memalign() распределяет байты размера и возвращает указатель на выделенную память». Вам следует избегать использования устаревших функций в новом коде и обновлять старый код для использования устаревшей альтернативы ( posix_memalign() или aligned_alloc() будет хорошим выбором). Я не знаю, что было сказано в сентябре 2012 года.
-1
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);
  • 0
    Я думаю, что есть проблема с этим, потому что ваше дополнение будет указывать на местоположение, которое не является malloc'd - Не уверен, как это работало на вашем.
  • 0
    да, это должно быть: добавить + (добавить% 16)?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню