Вопрос о компиляторах и как они работают

Это - код C, который освобождает память отдельно связанного списка. Это компилируется с Visual C++ 2008 и работы кода, как это должно быть.

/* Program done, so free allocated memory */
current = head;
struct film * temp;
temp = current;
while (current != NULL)
{
    temp = current->next;
    free(current);
    current = temp;
}

Но я также встретился (даже в книги) с тем же кодом, записанным как это:

/* Program done, so free allocated memory */
current = head;
while (current != NULL)
{
    free(current);
    current = current->next;
}

Если я компилирую тот код со своим VC ++ 2008, катастрофические отказы программы, потому что я сначала освобождаю текущий и затем присваиваюсь текущий-> рядом с током. Но очевидно если я скомпилирую этот код с некоторым другим компилятором (например, то компилятор, которые заказывают используемого автора) программа будет работать. Таким образом, вопрос, почему это кодирует скомпилированный с определенной работой компилятора? Это, потому что тот компилятор поместил инструкции в двоичный файл, которые помнят адрес тока-> затем, хотя я освободил текущий, и мой VC ++ не делает. Я просто хочу понять, как работают компиляторы.

12
задан dontoo 14 April 2010 в 10:25
поделиться

6 ответов

Вторая программа вызывает неопределенное поведение. Это не разница в компиляторе, а скорее в реализации стандартной библиотеки C и функции free (). Компилятор сохранит указатель current как локальную переменную, но не будет хранить копию памяти, на которую он ссылается.

Когда вы вызываете free (), вы отказываетесь от владения памятью, на которую указывает указатель, переданный в функцию free (). Возможно, что после того, как вы откажетесь от владения, содержимое указанной памяти будет по-прежнему разумным и по-прежнему действительными ячейками памяти в адресном пространстве вашего процесса. Следовательно, возможно, что доступ к ним будет работать (обратите внимание, что таким образом вы можете незаметно повредить память). Указатель, который не равен нулю и указывает на уже освобожденную память, известен как висячий указатель и невероятно опасен. То, что это может показаться работающим, не означает, что это правильно.

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

18
ответ дан 2 December 2019 в 04:02
поделиться

Это потому, что этот компилятор поместил инструкции в двоичный файл, которые запоминают адрес current-> next, хотя я освободил current, а мой VC ++ - нет.

Я так не думаю.

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

Вот пример с компилятором GCC (у меня нет VC ++)

struct film { film* next; };

int main() {
  film* current = new film();
  delete current;

  return 0;
}

;Creation
movl    $4, (%esp)   ;the sizeof(film) into the stack (4 bytes)
call    _Znwj        ;this line calls the 'new operator' 
                     ;the register %eax now has the pointer
                     ;to the newly created object

movl    $0, (%eax)   ;initializes the only variable in film

;Destruction
movl    %eax, (%esp) ;push the 'current' point to the stack
call    _ZdlPv       ;calls the 'delete operator' on 'current'

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

После уничтожения объекта и освобождения его памяти вы больше не можете безопасно ссылаться на current-> next.

0
ответ дан 2 December 2019 в 04:02
поделиться

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

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

1
ответ дан 2 December 2019 в 04:02
поделиться

Лучший способ узнать, как работают компиляторы, - это не спрашивать, как они обрабатывают недопустимый код. Вам нужно прочитать книгу (на самом деле не одну) о компиляции. Хорошее место для начала было бы проверить ресурсы на Обучение написанию компилятора .

4
ответ дан 2 December 2019 в 04:02
поделиться

После того, как вы выполнили free(current), память, на которую указывает current (где хранится current->next), была возвращена библиотеке C, поэтому вы больше не должны к ней обращаться.

Библиотека C может изменить содержимое этой памяти в любое время - что приведет к тому, что current->next будет поврежден - но она также может не изменять часть или все содержимое, особенно в ближайшее время. Вот почему это работает в некоторых средах и не работает в других.

Это как проезд на красный сигнал светофора. Иногда вам это сойдет с рук, но иногда вас переедет грузовик.

11
ответ дан 2 December 2019 в 04:02
поделиться

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

Приведите ссылки на книги, в которых вы видели этот пример, потому что его нужно исправить.

3
ответ дан 2 December 2019 в 04:02
поделиться