C-распределитель памяти и строгий псевдоним

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

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

Спасибо.

Изменить: Позвольте мне уточнить мой вопрос с помощью глупого простого примера:

// s == 0 frees the pool
void *my_custom_allocator(size_t s) {
    static void *pool = malloc(1000);
    static int in_use = FALSE;
    if( in_use || s > 1000 ) return NULL;
    if( s == 0 ) {
        in_use = FALSE;
        return NULL;
    }
    in_use = TRUE;
    return pool;
}

main() {
    int *i = my_custom_allocator(sizeof(int));
    //use int
    my_custom_allocator(0);
    float *f = my_custom_allocator(sizeof(float)); //not allowed...
}

Ответы

Ответ 1

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

И void*, возвращаемый malloc, не подпадает под действие правила строгого сглаживания, поскольку стандарт явно указывает, что указатель void может быть добавлен в любой другой вид указателя (и обратно). C99 раздел 7.20.3:

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


Что касается вашего обновления (пример), в котором вы фактически не возвращаете память в кучу, я думаю, что ваша путаница возникает из-за того, что выделенный объект обрабатывается специально. Если вы ссылаетесь на 6.5/6 на C99, вы увидите:

Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта, если он есть (сноска 75: Выделенные объекты не имеют объявленного типа).

Перечитайте эту сноску, это важно.

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

Если значение копируется в объект, не имеющий объявленного типа с использованием memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип измененного объекта для этого доступа и для последующих обращений, которые не изменяют value - это эффективный тип объекта, из которого копируется значение, если оно есть.

Для всех других обращений к объекту, не имеющему объявленного типа, эффективным типом объекта является просто тип lvalue, используемый для доступа.

Другими словами, выделенное содержимое блока станет типом элемента данных, который вы там разместили.

Если вы поместите там float, вы должны получить доступ к нему только как float (или совместимый тип). Если вы введете int, вы должны обработать его только как int (или совместимый тип).

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

Итак, если вы должны были хранить int там до освобождения в вашем коде, то перераспределите его как указатель float, вы не должны пытаться использовать float, пока вы на самом деле не положите его туда, До этой точки тип выделенного еще не float.

Ответ 2

Я отправляю этот ответ, чтобы проверить мое понимание строгих псевдонимов:

Строгое сглаживание имеет значение только тогда, когда происходит фактическое чтение и запись. Точно так же, как использование нескольких членов разного типа объединения одновременно - это поведение undefined, то же самое относится и к указателям: вы не можете использовать указатели другого типа для доступа к одной и той же памяти по той же причине, что вы не можете сделать это с помощью объединения.

Если вы рассматриваете только один из указателей как живой, то это не проблема.

  • Итак, если вы пишете через int* и читаете int*, это нормально.
  • Если вы пишете с помощью int* и читаете float*, это плохо.
  • Если вы пишете с помощью int*, а затем снова пишете, используя float*, прочитайте его с помощью float*, затем нажмите OK.

В случае нетривиальных распределителей у вас есть большой буфер, который вы обычно храните в char*. Затем вы делаете какую-то арифметику указателя, чтобы вычислить адрес, который вы хотите выделить, а затем разыщите его через структуры заголовка распределителя. Неважно, какие указатели вы используете, чтобы сделать указатель арифметикой только указатель, который вы разыскиваете область по всем вопросам. Поскольку в распределителе вы всегда это делаете с помощью структуры заголовка распределителя, вы не будете запускать поведение undefined.

Ответ 3

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

Надеюсь, это поможет!

Ответ 4

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

Трудность заключается в том, что для данной структуры и функции, например:

struct someStruct {unsigned char count; unsigned char dat[7]; }
void useStruct(struct someStruct s); // Pass by value

его можно вызвать так:

someStruct *p = malloc(sizeof *p);
p->count = 1;
p->dat[0] = 42;
useStruct(*p);

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