Подтвердить что ты не робот

Почему бесплатные сбои при вызове дважды

В C и С++ free(my_pointer) падает, когда он вызывается дважды.

Почему? Будут храниться все malloc вместе с размером. Когда вызывается первый free, он идентифицирует, что это было присвоено каким размером, поэтому нам не нужно передавать размер вместе со свободным вызовом.

Так как он знает все, почему он не проверяет второй раз и ничего не делает?

Либо я не понимаю, что поведение malloc/free или free не реализовано безопасно.

4b9b3361

Ответ 1

Вам не разрешено вызывать free в нераспределенной памяти, стандарт четко указывает (слегка перефразированный, мой акцент):

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

Что произойдет, например, если адрес, который вы дважды освобождаете, был перераспределен в середине нового блока, а код, который он выделил, только что сохранил что-то там, которое выглядело как настоящий заголовок блока malloc? Как:

 +- New pointer    +- Old pointer
 v                 v
+------------------------------------+
|                  <Dodgy bit>       |
+------------------------------------+

Хаос, это что.

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


И в отношении комментария:

... он может изящно общаться с конечным пользователем о том, что он удваивает свободное место.

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

Что произойдет, если:

  • thread Выделенная и освобожденная память по адресу 42.
  • поток B выделил память на адрес 42 и начал использовать его.
  • thread Освобождение этой памяти второй раз.
  • поток C назначил память адресу 42 и начал использовать его.

Затем у вас есть потоки B и C, которые думают, что они владеют этой памятью (они не обязательно должны быть потоками выполнения, я использую термин thread здесь как просто часть кода, который работает - все это может быть в один поток выполнения, но вызываемый последовательно).

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


Если вы хотите реализовать свою собственную оболочку вокруг free, вы можете сделать ее более безопасной (за счет небольшого снижения производительности), в частности, с помощью myFreeXxx вызовов ниже:

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

void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt  (int  **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }

int main (void) {
    char *x = malloc (1000);
    printf ("Before: %p\n", x);
    myFreeChar (&x);
    printf ("After:  %p\n", x);
    return 0;
}

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

  • освободить память; и
  • установите указатель на NULL.

Этот последний бит означает, что если вы снова попытаетесь освободить указатель, он ничего не сделает (потому что освобождение NULL специально включено стандартом).

Он не защитит вас от всех ситуаций, например, если вы сделаете копию указателя в другом месте, освободите оригинал, затем освободите копию:

char *newptr = oldptr;
myFreeChar (&oldptr);     // frees and sets to NULL.
myFreeChar (&newptr);     // double-free because it wasn't set to NULL.

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

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

void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt  (int  **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }
#define myFree(x) _Generic((x), \
    int** :  myFreeInt,  \
    char**:  myFreeChar, \
    default: myFreeVoid  )(x)

int main (void) {
    char *x = malloc (1000);
    printf ("Before: %p\n", x);
    myFree (&x);
    printf ("After:  %p\n", x);
    return 0;
}

С этим вы просто вызываете myFree и выбираете правильную функцию, основанную на типе.

Ответ 2

Возможно, вы неверно истолковали свое поведение. Если он сработает сразу же, он будет реализован в безопасном режиме. Я могу засвидетельствовать, что это было не обычное поведение бесплатно() много лет назад. Типичная реализация CRT тогда вообще не проверяла. Быстро и яростно, это просто повредило бы внутреннюю структуру кучи, испортив цепи распределения.

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

Это не распространено больше для современных реализаций кучи ЭЛТ или ОС. Такое поведение undefined очень доступно для использования вредоносными программами. И это облегчает вашу жизнь, вы быстро найдете ошибку в своем коде. В течение последних нескольких лет он не позволял мне избавиться от неприятностей, им не приходилось долгое время отлаживать неразрывную кучу коррупции. Хорошая вещь.

Ответ 3

почему он не проверяет второй раз, когда он не может найти какой-либо выделенный размер для второго бесплатного вызова()

Дополнительная проверка самой функции free() приведет к замедлению работы вашей программы во всех правильных случаях. Вы не должны делать двойную свободную. Управление памятью - ваша ответственность как программист; Невозможность сделать это - ошибка программирования. Это часть философии C: она дает вам всю необходимую силу, но, как следствие, позволяет легко стрелять себе в ногу.

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

Ответ 4

Хороший вопрос. Как вы заметили, malloc и free обычно выполняют некоторую форму бухгалтерского учета, часто в нескольких байтах, предшествующих распределению. Но подумайте об этом так:
  • Malloc некоторая память - добавляет бухгалтерские данные.
  • Свободно - память возвращается в пул.
  • Вы или кто-то еще malloc еще немного памяти, которая может включать или не включать или выравнивать со старым распределением.
  • Вы снова освободите старый указатель.

Куча (код для malloc бесплатного управления) в этот момент уже потеряла следы и/или перезаписала бухгалтерские данные, потому что память вернулась в кучу!

Следовательно, сбои. Единственный способ обеспечить это - это помнить о каждом размещении, когда-либо создаваемом в базе данных, которое будет расти без ограничений. Поэтому они этого не делают. Вместо этого помните, что вы не должны быть двойными.:)

Ответ 5

Вы говорите:

непонятно почему. есть бухгалтерия каждого malloc() вместе с размером.

Не обязательно. Я расскажу немного о dlmalloc (используется в glibc, uClibc,...).

Dlmalloc отслеживает блоки свободного пространства. Не может быть двух смежных свободных блоков, они сразу объединяются. Выделенные блоки не отслеживаются вообще! Выделенные блоки имеют некоторое запасное пространство для информации бухгалтерского учета (размер этого блока, размер предыдущего блока и некоторые флаги). Когда выделенный блок является бесплатным() 'd, dlmalloc вставляет его в двусвязный список.

Конечно, все это лучше объясняется в этой статье dlmalloc