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

В C и C++, free(my_pointer) катастрофические отказы, когда это называют дважды.

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

Так как это знает каждую вещь, почему это не проверяет во второй раз вокруг и ничего не делает?

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

18
задан dandan78 17 February 2019 в 09:38
поделиться

5 ответов

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

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

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

3
ответ дан 30 November 2019 в 06:42
поделиться

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

  1. Malloc - выделение некоторой памяти - добавляет данные бухгалтерского учета.
  2. Освобождаем ее - память возвращается в пул.
  3. Вы или кто-то другой выделяет еще немного памяти, которая может включать или не включать старое распределение.
  4. Вы снова освобождаете старый указатель.

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

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

3
ответ дан 30 November 2019 в 06:42
поделиться

Вы говорите:

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

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

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

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

1
ответ дан 30 November 2019 в 06:42
поделиться

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

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

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

9
ответ дан 30 November 2019 в 06:42
поделиться

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

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

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

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

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

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


И относительно комментария:

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

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

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

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

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

Нет, я думаю, что текущие 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 , и он выбирает правильную функцию в зависимости от типа.

30
ответ дан 30 November 2019 в 06:42
поделиться