Оригінальна відповідь
{
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()
повинен повертати покажчик , який досить добре вирівняний для будь-яких . Цілей Однак сенс «любих», перш за все , для таких речей , як основні типів - long
, double
, long double
, long long
., І покажчики на об'єкти і покажчики на функцію Коли ви роблячи більш спеціалізовані речі, наприклад, граючи з графічними системами, їм може знадобитися більш жорстке вирівнювання, ніж решта системи - отже, такі питання та відповіді.)
Наступним кроком є перетворення покажчика недійсності на покажчик char; Незважаючи на 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 , яке ще раз повторило точку, яку вперше висловив Білл К кілька років тому, яку мені вдалося не помітити до цих пір.]
#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()
на функцію розподілу загального призначення, єдине повернене значення з розподільника повинно було б кодувати адресу випуску, як вказали кілька людей у своїх відповідях.
Проблеми з інтерв'юерами
Урі прокоментував: Можливо, у мене сьогодні є проблема з розумінням читання сьогодні, але якщо на запитання інтерв'ю конкретно сказано: "Як би ви виділили 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
Повертає
The 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 була варіантом, коли на запитання було дано відповідь.
Позаду, нова вирівняна функція пам’яті виконує таку ж роботу, як описана в запитанні, за винятком того, що вони мають можливість легше примусовувати вирівнювання і внутрішньо слідкувати за початком вирівняної пам’яті, щоб код не доведеться мати справу з спеціально - це просто звільняє пам'ять, повернуту функцією розподілу, яка використовувалася.