Я только что закончил тест в рамках собеседования, и один вопрос поставил меня в тупик, даже используя Google для справки. Я хотел бы посмотреть, что команда StackOverflow может сделать с этим:
memset_16aligned
Функция требует 16 байт , выровненный указатель , переданный ему, или это будет крах.а) Как бы вы разместили 1024 байта памяти и выровняли ее по 16-байтовой границе?
б) Освободить память после того,memset_16aligned
как выполнено.
{
void *mem;
void *ptr;
// answer a) here
memset_16aligned(ptr, 0, 1024);
// answer b) here
}
c
memory-management
JimDaniel
источник
источник
Ответы:
Оригинальный ответ
Фиксированный ответ
Объяснение по запросу
Первый шаг - выделить достаточно свободного места на всякий случай. Поскольку память должна быть выровнена на 16 байтов (это означает, что адрес начального байта должен быть кратным 16), добавление 16 дополнительных байтов гарантирует, что у нас будет достаточно места. Где-то в первых 16 байтах есть 16-байтовый выровненный указатель. (Обратите внимание , что
malloc()
должен возвращать указатель , который достаточно хорошо выровненный для любых . Целей Однако смысл «любых», прежде всего , для таких вещей , как основные типов -long
,double
,long double
,long long
., И указатели на объекты и указатели на функцию Когда вы При выполнении более специализированных задач, таких как игра с графическими системами, им может потребоваться более строгое выравнивание, чем остальной системе - отсюда и такие вопросы и ответы.)Следующим шагом является преобразование пустого указателя в указатель на символ; GCC, несмотря на это, вы не должны выполнять арифметику указателей на пустых указателях (и GCC имеет опции предупреждения, чтобы сообщить вам, когда вы злоупотребляете им). Затем добавьте 16 к стартовому указателю. Предположим,
malloc()
вы вернули вам неправильно выровненный указатель: 0x800001. Добавление 16 дает 0x800011. Теперь я хочу округлить до 16-байтовой границы - поэтому я хочу сбросить последние 4 бита до 0. 0x0F имеет последние 4 бита, равные единице; следовательно,~0x0F
все биты установлены в один, кроме последних четырех. И, что с 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 <inttypes.h>
. [Этот код включает исправление, указанное CR , который повторял точку зрения, впервые высказанную Биллом К несколько лет назад, которую мне удалось пропустить до сих пор.]И вот немного более обобщенная версия, которая будет работать для размеров, которые имеют степень 2:
Чтобы преобразовать
test_mask()
в функцию распределения общего назначения, единственное возвращаемое значение от распределителя должно было бы закодировать адрес выпуска, как несколько человек указали в своих ответах.Проблемы с интервьюерами
Ури прокомментировал: «Может быть, у меня сегодня утром проблема с пониманием прочитанного, но если вопрос об интервью конкретно говорит:« Как бы вы распределили 1024 байта памяти », а вы явно выделяете больше, чем это? Не будет ли это автоматическим отказом интервьюера?
Мой ответ не помещается в комментарий из 300 символов ...
Это зависит, я полагаю. Я думаю, что большинство людей (включая меня) восприняли вопрос так: «Как бы вы распределили пространство, в котором можно хранить 1024 байта данных, и где базовый адрес кратен 16 байтам». Если интервьюер действительно имел в виду, как вы можете выделить 1024 байта (только) и выровнять его по 16 байтов, то варианты более ограничены.
Однако, если бы интервьюер ожидал какого-либо из этих ответов, я бы ожидал, что они признают, что это решение отвечает на тесно связанный вопрос, а затем пересмотрят свой вопрос, чтобы направить разговор в правильном направлении. (Кроме того, если интервьюер стал действительно неуклюжим, я бы не хотел работать; если ответ на недостаточно точное требование сгорел без исправления, тогда интервьюер - это не тот, для кого безопасно работать.)
Мир движется дальше
Название вопроса недавно изменилось. Это было Решить выравнивание памяти в вопросе C интервью, которое озадачило меня . Пересмотренный заголовок (« Как распределить память только с помощью стандартной библиотеки?» ) Требует немного пересмотренного ответа - это дополнение содержит его.
C11 (ISO / IEC 9899: 2011) добавлена функция
aligned_alloc()
:И POSIX определяет
posix_memalign()
:Любой или оба из них можно было бы использовать для ответа на вопрос сейчас, но только функция POSIX была опцией, когда на вопрос был первоначально дан ответ.
За кулисами новая выровненная функция памяти выполняет почти ту же работу, что и описанную в вопросе, за исключением того, что она позволяет более легко форсировать выравнивание и отслеживать внутреннее начало выровненной памяти, чтобы код приходится иметь дело специально - он просто освобождает память, возвращаемую функцией выделения, которая использовалась.
источник
<inttypes.h>
доступный от C99 (по крайней мере для строки формата - возможно, значения должны быть переданы с помощью приведения:)(uintptr_t)mem, (uintptr_t)ptr
. Строка формата основана на конкатенации строк, а макрос PRIXPTR - это корректный указательprintf()
длины и типа для шестнадцатеричного выводаuintptr_t
значения. Альтернативой является использование,%p
но вывод этого зависит от платформы (некоторые добавляют начальные0x
, большинство - нет) и, как правило, пишутся шестнадцатеричными строчными буквами, что мне не нравится; то, что я написал, одинаково для разных платформ.Три несколько разных ответа в зависимости от того, как вы смотрите на вопрос:
1) Достаточно хорошо для точного задаваемого вопроса является решение Джонатана Леффлера, за исключением того, что для округления до 16 выровнено, вам нужно только 15 дополнительных байтов, а не 16.
A:
B:
2) Для более общей функции выделения памяти вызывающая сторона не хочет отслеживать два указателя (один для использования и один для освобождения). Таким образом, вы сохраняете указатель на «настоящий» буфер под выровненным буфером.
A:
B:
Обратите внимание, что в отличие от (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-битный целочисленный тип). Требования выравнивания для этого объединения можно определить, внедрив его в структуру с одним символом, за которым следует объединение:Затем вы будете использовать большее из запрошенного выравнивания (в примере 16) и
align
значение, рассчитанное выше.На (64-разрядной) ОС Solaris 10
malloc()
создается впечатление, что базовое выравнивание для результата кратно 32 байтам.]
На практике выровненные распределители часто принимают параметр для выравнивания, а не для аппаратного соединения. Таким образом, пользователь передаст размер структуры, которая ему небезразлична (или наименьшая степень 2 больше или равна этой величине), и все будет хорошо.
3) Используйте то, что предоставляет ваша платформа:
posix_memalign
для POSIX,_aligned_malloc
в Windows.4) Если вы используете C11, то самый чистый - портативный и лаконичный вариант - использовать стандартную библиотечную функцию,
aligned_alloc
которая была представлена в этой версии спецификации языка.источник
ASSERT(mem);
для проверки результатов распределения;assert
для ловли ошибок программирования и нехватки ресурсов времени выполнения.char *
и asize_t
приведет к ошибке. Вы должны использовать что-то вродеuintptr_t
.Вы также можете попробовать
posix_memalign()
(на платформах POSIX, конечно).источник
Вот альтернативный подход к части «округления». Не самое блестяще закодированное решение, но оно выполняет свою работу, и этот тип синтаксиса немного легче запомнить (плюс будет работать для значений выравнивания, которые не являются степенью 2). Приведение
uintptr_t
было необходимо, чтобы успокоить компилятор; арифметика указателей не очень любит деление или умножение.источник
К сожалению, в C99 кажется довольно сложно гарантировать какое-либо выравнивание способом, который был бы переносим на любую реализацию C, соответствующую C99. Почему? Поскольку указатель не гарантированно является «байтовым адресом», который можно представить с помощью плоской модели памяти. Также не гарантировано представление uintptr_t , которое в любом случае является необязательным типом.
Мы могли бы знать о некоторых реализациях, которые используют представление для void * (и по определению также char * ), который является простым байтовым адресом, но в C99 он непрозрачен для нас, программистов. Реализация может представлять указатель с помощью набора { сегмент , смещение }, где смещение может иметь выравнивание «кто знает, что» в реальности. Да, указатель может даже быть некоторой формой значения поиска в хеш-таблице или даже значением поиска в связанном списке. Это может кодировать информацию о границах.
В недавнем черновике C1X для стандарта C мы видим ключевое слово _Alignas . Это может помочь немного.
Единственная гарантия, которую дает нам C99, состоит в том, что функции выделения памяти будут возвращать указатель, подходящий для назначения указателю, указывающему на любой тип объекта. Поскольку мы не можем указать выравнивание объектов, мы не можем реализовать наши собственные функции выделения с ответственностью за выравнивание четко определенным, переносимым способом.
Было бы хорошо ошибиться в этом утверждении.
источник
aligned_alloc()
. (C ++ 11/14 / 1z все еще не имеет его)._Alignas()
и C ++alignas()
ничего не делает для динамического размещения, только для автоматического и статического хранения (или структурного размещения).На фронте заполнения счетчика 16 по 15 байтов фактическое число, которое нужно добавить, чтобы получить выравнивание N, равно max (0, NM), где M - естественное выравнивание распределителя памяти (и оба - степени 2).
Так как минимальное выравнивание памяти любого распределителя составляет 1 байт, 15 = max (0,16-1) является консервативным ответом. Однако, если вы знаете, что ваш распределитель памяти будет выдавать вам 32-битные адреса, выровненные по int (что довольно часто), вы могли бы использовать 12 в качестве пэда.
Это не важно для этого примера, но это может быть важно для встраиваемой системы с 12 КБ ОЗУ, где каждый сохраненный int имеет значение.
Лучший способ реализовать это, если вы на самом деле попытаетесь сохранить каждый возможный байт, - это использовать макрос как исходное выравнивание памяти. Опять же, это, вероятно, полезно только для встроенных систем, где вам нужно сохранять каждый байт.
В приведенном ниже примере в большинстве систем значение 1 вполне подходит
MEMORY_ALLOCATOR_NATIVE_ALIGNMENT
, однако для нашей теоретической встроенной системы с 32-разрядным выравниванием выделенного пространства следующее может сэкономить немного драгоценной памяти:источник
Возможно они были бы удовлетворены знанием memalign ? И, как отмечает Джонатан Леффлер, есть две новые предпочтительные функции, о которых нужно знать.
Ой, Флорин победил меня в этом. Однако, если вы прочитаете справочную страницу, на которую я ссылаюсь, вы, скорее всего, поймете пример, предоставленный более ранним постером.
источник
memalign
функция устарела иaligned_alloc
илиposix_memalign
вместо них следует использовать». Я не знаю, что он сказал в октябре 2008 года, но, вероятно, не упомянул,aligned_alloc()
как это было добавлено в C11.Мы делаем такие вещи постоянно для Accelerate.framework, сильно векторизованной библиотеки OS X / iOS, где мы должны постоянно уделять внимание выравниванию. Есть довольно много вариантов, один или два из которых я не видел упомянутых выше.
Самый быстрый метод для такого маленького массива - просто положить его в стек. С GCC / Clang:
Бесплатно () не требуется. Обычно это две инструкции: вычтите 1024 из указателя стека, затем И указатель стека с -alignment. Предположительно, запрашивающему потребовались данные в куче, потому что его срок службы массива превысил стек, или рекурсия работает, или пространство стека стоит серьезной премии.
В OS X / iOS все вызовы malloc / calloc / и т. Д. всегда выровнены по 16 байтов. Например, если вам нужно выровнять 32 байта для AVX, вы можете использовать posix_memalign:
Некоторые люди упоминали интерфейс C ++, который работает аналогично.
Не следует забывать, что страницы выровнены с большой степенью двойки, поэтому выровненные по размеру буферы также выровнены по 16 байтов. Таким образом, mmap () и valloc () и другие подобные интерфейсы также являются опциями. Преимущество mmap () в том, что буфер может быть выделен предварительно инициализированным с чем-то ненулевым, если хотите. Так как у них размер страницы выровнен, вы не получите от них минимальное выделение, и он, вероятно, будет подвержен сбоям виртуальной машины при первом касании.
Сырный: Включите охрану malloc или подобное. Буферы размером n * 16 байтов, такие как этот, будут выровнены по n * 16 байтов, потому что VM используется для перехвата, и ее границы находятся на границах страницы.
Некоторые функции Accelerate.framework используют предоставленный пользователем временный буфер для использования в качестве рабочего пространства. Здесь мы должны предположить, что переданный нам буфер сильно смещен, и пользователь активно пытается усложнить нашу жизнь. (Наши тестовые примеры прикрепляют защитную страницу прямо перед и после временного буфера, чтобы подчеркнуть злобу.) Здесь мы возвращаем минимальный размер, который нам нужен, чтобы гарантировать 16-байтовый выровненный сегмент где-то в нем, а затем вручную выравниваем буфер после. Этот размер - требуемый_размер + выравнивание - 1. Итак, в этом случае это 1024 + 16 - 1 = 1039 байт. Затем выровняйте так:
Добавление alignment-1 переместит указатель за первый выровненный адрес, а затем ANDing с -alignment (например, 0xfff ... ff0 для alignment = 16) вернет его на выровненный адрес.
Как описано в других статьях, в других операционных системах без 16-байтовых гарантий выравнивания вы можете вызывать malloc с большим размером, позже выделить указатель free (), затем выполнить выравнивание, как описано выше, и использовать выровненный указатель, так же как описано для нашего временного буфера.
Что касается align_memset, это довольно глупо. Вам нужно только зациклить до 15 байтов для достижения выровненного адреса, а затем продолжить с выровненными хранилищами с некоторым возможным кодом очистки в конце. Вы можете даже выполнить очистку битов в векторном коде, либо в виде невыровненных хранилищ, которые перекрывают выровненную область (при условии, что длина равна по крайней мере длине вектора), либо используя что-то вроде movmaskdqu. Кто-то просто ленится. Тем не менее, это, вероятно, разумный вопрос для интервью, если интервьюер хочет знать, довольны ли вы stdint.h, побитовыми операторами и основами памяти, поэтому надуманный пример можно простить.
источник
Я удивлен, что никто не проголосовал за ответ Шао , что, насколько я понимаю, невозможно сделать то, что просят в стандартном C99, поскольку формальное преобразование указателя на интегральный тип - неопределенное поведение. (Помимо стандарта, разрешающего преобразование <-> , но стандарт, по-видимому, не позволяет выполнять какие-либо манипуляции со значением и затем преобразовывать его обратно.)
uintptr_t
void*
uintptr_t
источник
unsigned char* myptr
; а затем вычислить `mptr + = (16- (uintptr_t) my_ptr) & 0x0F, поведение будет определено во всех реализациях, которые определяют my_ptr, но будет ли выровнен результирующий указатель, будет зависеть от отображения между битами uintptr_t и адресами.использование memalign, Aligned-Memory-Blocks может быть хорошим решением для этой проблемы.
источник
memalign
функция устарела иaligned_alloc
илиposix_memalign
вместо них следует использовать». Я не знаю, что это было сказано в октябре 2010 года.Первое, что пришло мне в голову при чтении этого вопроса, было определить выровненную структуру, создать ее экземпляр, а затем указать на нее.
Есть ли фундаментальная причина, по которой я скучаю, поскольку никто другой не предложил это?
В качестве идентификатора, поскольку я использовал массив char (предполагая, что системный char равен 8 битам (т.е. 1 байт)), я не вижу необходимости в этом
__attribute__((packed))
(исправьте меня, если я ошибаюсь), но я поставил его в любом случае.Это работает на двух системах, на которых я его пробовал, но возможно, что существует оптимизация компилятора, о которой я не подозреваю, что она дает мне ложные срабатывания в отношении эффективности кода. Я использовал
gcc 4.9.2
на OSX иgcc 5.2.1
на Ubuntu.источник
Специфичные для MacOS X:
C11 поддерживается, так что вы можете просто вызвать align_malloc (16, size).
MacOS X выбирает код, оптимизированный для отдельных процессоров во время загрузки для memset, memcpy и memmove, и этот код использует трюки, о которых вы никогда не слышали, чтобы сделать его быстрым. 99% вероятности, что memset работает быстрее, чем любой рукописный memset16, что делает весь вопрос бессмысленным.
Если вы хотите 100% портативное решение, до C11 его нет. Потому что нет портативного способа проверить выравнивание указателя. Если он не должен быть на 100% портативным, вы можете использовать
Это предполагает, что выравнивание указателя сохраняется в младших битах при преобразовании указателя в unsigned int. Преобразование в unsigned int теряет информацию и определяется реализацией, но это не имеет значения, потому что мы не конвертируем результат обратно в указатель.
Ужасная часть, конечно, в том, что оригинальный указатель должен быть где-то сохранен, чтобы вызвать с ним функцию free (). В общем, я бы действительно усомнился в мудрости этого дизайна.
источник
aligned_malloc
в OS X? Я использую Xcode 6.1, и он нигде не определен в iOS SDK, и нигде не объявлен/usr/include/*
.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
изменил сообщения об ошибках.Вы также можете добавить около 16 байтов и затем выровнять исходный ptr в 16 бит, добавив (16-mod), как показано под указателем:
источник
Если существуют ограничения, вы не можете тратить один байт, тогда это решение работает: Примечание: есть случай, когда это может выполняться бесконечно: D
источник
%
оператор определенvoid*
значимым образом?Для решения я использовал концепцию заполнения, которая выравнивает память и не тратит память одного байта.
Если есть ограничения, вы не можете тратить ни одного байта. Все указатели, выделенные с помощью malloc, выровнены по 16 байтов.
C11 поддерживается, так что вы можете просто позвонить
aligned_alloc (16, size)
.источник
malloc()
действительно выровнен по 16-байтовой границе, но ничто в любом стандарте не гарантирует, что он просто будет достаточно хорошо выровнен для любого использования, а во многих 32-битных системах при выравнивании Достаточно 8-байтовой границы, а для некоторых достаточно 4-байтовой границы.Надеюсь, что это самая простая реализация, дайте мне знать ваши комментарии.
источник
источник
add += 16 - (add % 16)
.(2 - (2 % 16)) == 0
,