мой последний тест провален, но я думаю, что нашел решение, как его пройти. он предполагает использование alignof(max_align_t) (или даже alignof(long double)), но я не совсем уверен, правильно ли я понимаю выравнивание распределения.
Моя идея состоит в том, что если фактический размер моего типа не делится на позицию адреса, мне нужно добавить некоторые дополнения, пока условие не будет выполнено. этот процесс помогает компилятору более эффективно обращаться к памяти, создавая предсказуемый шаблон!
если я прав, то почему последний тест не проходит? в противном случае разъяснения были бы оценены!
void *malloc(size_t sz, const char *file, int line) {
(void)file, (void)line; // avoid uninitialized variable warnings
if (default_buffer.pos + sz > default_buffer.size) {
// Not enough space left in default buffer for allocation
gstats.nfail++;
gstats.fail_size += sz;
return nullptr;
}
// Here i'm checking for correct allocation alignement
if (default_buffer.pos % sz != 0) {
default_buffer.pos += default_buffer.pos % sz;
}
// Otherwise there is enough space; claim the next `sz` bytes
void *ptr = &default_buffer.buffer[default_buffer.pos];
default_buffer.pos += sz;
gstats.ntotal++;
gstats.total_size += sz;
return ptr;
}
test.c
int main() {
double* ptr = (double*) malloc(sizeof(double));
assert((uintptr_t) ptr % alignof(double) == 0);
assert((uintptr_t) ptr % alignof(unsigned long long) == 0);
assert((uintptr_t) ptr % alignof(std::max_align_t) == 0);
char* ptr2 = (char*) malloc(1);
assert((uintptr_t) ptr2 % alignof(double) == 0);
assert((uintptr_t) ptr2 % alignof(unsigned long long) == 0);
assert((uintptr_t) ptr2 % alignof(std::max_align_t) == 0);
free(ptr);
free(ptr2);
}
Я уже решил проблему и хочу понять, почему альтернатива моему решению не работает на конкретном тесте. По сути, я стремлюсь убедиться в правильности моего понимания!
pos += pos % sz; не совпадает с sz. Вам нужно сделать pos = (pos + sz - 1) & ~(sz - 1);, предполагая, что sz является степенью 2. Это гарантирует только выравнивание pos как индекса в вашем буфере. Он не учитывает собственное выравнивание буфера. Но если вы выровняли свой буфер по sizeof(max_align_t), тогда все будет в порядке.
Обратите внимание на вышеизложенное: я не совсем правильно прочитал, что вы на самом деле не используете значение выравнивания, а просто полагаетесь на sz. Для этого вам нужно будет вычислить наиболее подходящее выравнивание для sz как степень двойки, а затем выполнить согласование с ним.
@paddy, так что, если sz является степенью 2, то мне нужно вычислить для него выравнивание, а в противном случае мне следует найти ближайшую степень двойки и использовать ее для выравнивания?
Да, наивный способ — начать с 1 и продолжать сдвигать влево, пока не достигнете sizeof(max_align_t) или не станет равным/превысит размер выделения. Более разумный способ — вычислить log-base-2 вашего размера и обрезать его до максимального выравнивания, что, вероятно, будет быстрее.
@paddy: Используйте _Alignof (max_align_t), а не sizeof(max_align_t). Кроме того, использование пробела между _Alignof или sizeof и его операндом в круглых скобках может помочь напомнить программистам, что это оператор, а не функция, а вещь после него — это имя типа в скобках, а не список аргументов.
@paddy: Re «… продолжайте сдвигать влево, пока не достигнете… или не станет равным/превысите размер выделения»: выравнивание объекта не превышает наибольшую степень двойки, которая делит его размер. Например, если размер объекта составляет 12 байт, его требование к выравниванию составляет не более 4 байтов. Нет необходимости переходить к 16. Наибольшую степень двойки, которая делит size, можно рассчитать как size & -size.
Почему в вопросе с пометкой C есть файл с именем test.cc? .cc предназначен для исходных файлов C++, а в C++ действуют другие правила, чем в C.
Ого, это крутой трюк. Спасибо также за разъяснение требований к выравниванию. Это имеет гораздо больше смысла. Я обновлю ответ соответственно.
При присвоении имени вашей подпрограмме поведение программы не определяется стандартом C, и могут возникнуть проблемы с компилятором, если вы не примете меры предосторожности, чтобы отключить встроенную информацию компилятора о malloc.
я изменил название своей функции, чтобы прояснить свой вопрос





Как я уже упоминал в комментариях, ваш расчет выравнивания неверен. Представьте, что вам нужно выравнивание 4, а остаток по модулю равен 3. Вы не будете добавлять еще 3 — вы добавите 4 минус 3.
Вместо использования по модулю типичный способ выравнивания значения — добавить на единицу меньше, чем выравнивание (чтобы все, кроме нуля, гарантированно приводило к адресу на следующей границе выравнивания), а затем замаскировать все биты ниже, чем выравнивание:
pos = (pos + align - 1) & ~(align - 1);
Обратите внимание, что align должно быть беззнаковой степенью 2.
Поскольку здесь вы пытаетесь выровнять позицию в буфере, вы также должны быть осторожны, чтобы на самом деле это привело к такому же выравниванию в памяти.
По поводу автоматического выравнивания пользователь Эрик Постпишиль также прокомментировал:
Выравнивание объекта не превышает наибольшую степень двойки, делящую его размер. Например, если размер объекта составляет 12 байт, его требование к выравниванию составляет не более 4 байтов. Нет необходимости переходить к 16. Наибольшую степень двойки, которая делит
size, можно рассчитать какsize & -size.
Итак, сложив все это вместе, вот простой способ выравнивания по произвольному адресу:
#include <stddef.h>
#include <stdint.h>
size_t max_align(size_t size)
{
size_t alignment = size & -size;
return ((alignment - 1) & (sizeof(max_align_t) - 1)) + 1;
}
void* align(void* ptr, size_t alignment)
{
return (void*)(((uintptr_t)ptr + alignment - 1) & ~(alignment - 1));
}
Обратите внимание на грязный трюк с обрезкой до sizeof(max_align_t). Это может быть или не быть лучше, чем простое введение ветки. Я не такой уж и грубый!
Посмотрите это в действии:
void test(char* buffer, size_t size)
{
size_t alignment = max_align(size);
char* aligned = align(buffer, alignment);
size_t offset = aligned - buffer;
printf("-align=%p size=%zu alignment=%zu offset=%zu\n",
aligned, size, alignment, offset);
}
struct foo {
int a;
char b[2];
int c;
void* d;
};
struct __attribute((packed)) bar {
int a;
char b[2];
int c;
void* d;
};
int main(void)
{
char buffer[256];
for (size_t offset = 0; offset < 64; offset += 13) {
char* base = buffer + offset;
printf("\nbuffer=%p :\n", base);
test(base, sizeof(struct foo));
test(base, sizeof(struct bar));
}
}
Пример вывода:
buffer=0x7ffc98db0770 :
-align=0x7ffc98db0770 size=24 alignment=8 offset=0
-align=0x7ffc98db0770 size=18 alignment=2 offset=0
buffer=0x7ffc98db077d :
-align=0x7ffc98db0780 size=24 alignment=8 offset=3
-align=0x7ffc98db077e size=18 alignment=2 offset=1
buffer=0x7ffc98db078a :
-align=0x7ffc98db0790 size=24 alignment=8 offset=6
-align=0x7ffc98db078a size=18 alignment=2 offset=0
buffer=0x7ffc98db0797 :
-align=0x7ffc98db0798 size=24 alignment=8 offset=1
-align=0x7ffc98db0798 size=18 alignment=2 offset=1
buffer=0x7ffc98db07a4 :
-align=0x7ffc98db07a8 size=24 alignment=8 offset=4
-align=0x7ffc98db07a4 size=18 alignment=2 offset=0
Как вы упомянули в комментариях, вы, вероятно, могли бы использовать здесь что-то вроде _BitScanReverse64 (msvc) или __builtin_clzll (gcc) вместо цикла while.
Оказывается, в этом нет необходимости, потому что мой подход к выравниванию по размеру объекта был неправильным. Я отредактировал соответственно. Обратите внимание, что расчет выравнивания может привести к завышению оценки выравнивания. В моем примере кода max_align(sizeof(struct bar)) возвращает 2, но встроенный alignof(struct bar) на самом деле возвращает 1, потому что это упакованная структура.
Вы не передаете никакой информации о типе в свой «malloc»; просто размер. Ваш код предполагает, что «alignof» — это то же самое, что «sizeof», но это часто не так.