Бесплатная матрица, как бесплатно работает в этом случае - PullRequest
0 голосов
/ 04 мая 2018

У меня есть динамически выделенная матрица из char или что-то вроде этого:

char **ptr;
ptr = (char**)malloc(sizeof(char*) * 3);
*ptr = (char*)malloc(4);
*(ptr + 1) = (char*)malloc(4);
*(ptr + 2) = 0;

Если я освобожу строки матрицы без свободной памяти указателей типа

int i;
for(i = 0; i < 2; i++)
    free(*(ptr + i));

освободит все строки, кроме последней, перед нулевым указателем, в этом случае будет свободна только строка * ptr. Чтобы освободить и последний, нам нужно добавить еще одну инструкцию

free(ptr);

Вопрос в том, как это работает, и почему мы не можем освободить только память указателей или только строковую память этой матрицы?

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

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

Во-первых, вы, кажется, думаете о матрице как о выделенной в памяти форме визуальной матрицы. Но вы должны помнить, что компилятор не имеет понятия о матрице. Он также не знает, что память, которую вы выделяете в качестве отправной точки в строке матрицы, указывает на строку.

Во-вторых, вы должны явно указать компилятору, что вы хотите, а не полагаться на догадки компилятора, что также может привести к неопределенному поведению.

Я буду использовать 8-битные адреса, чтобы проиллюстрировать это.

Взять ptr = (char**)malloc(sizeof(char*) * 3), он может выделять адреса на 0x30 0x31 0x32, каждый из которых будет хранить 8-битный тип. Предположим, что вы делаете *(ptr + 0) = malloc(4). Вызов malloc вернет указатель на четыре последовательных местоположения, где могут храниться символы. Предположим, он возвращает 0x40, это означает, что все адреса 0x40 0x41 0x42 0x43 доступны для использования.

Однако значение по адресу 0x30 назначается 0x40, теперь помните, что указатель - это не тип, а двоичное представление числа, которое происходит для представления адреса. Следовательно, указатели не имеют такой роскоши, как деаллокатор (например, деструктор в C ++). Компилятор не знает, что это значение используется для доступа к выделенному адресу памяти, а скорее сохраняет это значение для вас. По этой причине вызов free(ptr) освободит только адреса 0x30 0x31 0x32.

Проще говоря, предположим, что у вас есть четыре целых числа, хранящихся в int* как 1 2 3 4, если компилятор попытается освободить четыре целых числа как адреса, вы столкнетесь с проблемами, поскольку буквально говорит компилятор с free адресами 0x01 0x02 0x03 0x4, который в старой системе будет иметь ужасные последствия, а в современных системах ваше приложение будет прекращено.

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

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

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

0 голосов
/ 04 мая 2018

Вы всегда должны делать то же самое число освобождений, что и для malloc.

Вы можете переписать свой код как:

char **ptr;
ptr = malloc(sizeof(char*) * 3); //A
*(ptr + 0) = malloc(4); //B
*(ptr + 1) = malloc(4); //B
*(ptr + 2) = 0; //C

Вы Malloc 3 раза:

  • A. Вы выделяете массив для хранения трех указателей на массивы символов.
  • B. Вы выделяете массив из 4 символов и назначаете его одному из значений указателя, созданных вами в A.
  • C. Вы устанавливаете последний элемент A. на 0. Это то же самое, что установить NULL. Но, Дэвид отмечает, что установка NULL лучше. Это специальное значение, называемое нулевым указателем.

Во-первых, освобождение ptr не будет работать, так как вы потеряете значения *(ptr+0) и т. Д. После этого вы не сможете их освободить. Таким образом, вы должны сначала освободить *(ptr+0) и *(ptr+1), как вы их Malloc'ed. Но вам не придется освобождать *(ptr+2), так как вы не использовали его неправильно.

*(ptr + 0) и *(ptr + 1) - это всего лишь указатели на то, что вы выделили с помощью (char*)malloc(4);. Освобождение ptr освободит сами указатели, а не память, на которую они указывают. Бесплатно не рекурсивно. Поэтому вы должны освободить отдельные указатели, прежде чем сможете освободить массив указателей.

...