Вопрос о компиляторах и как они работают - PullRequest
12 голосов
/ 14 апреля 2010

Это код 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, произойдет сбой программы, поскольку я сначала освобождаю текущий, а затем назначаю текущий -> рядом с текущим. Но, очевидно, если я скомпилирую этот код с другой компилятором (например, компилятором, который использовал автор книги), программа будет работать. Итак, вопрос в том, почему этот код скомпилирован с конкретным компилятором? Это потому, что этот компилятор поместил инструкции в двоичный файл, которые помнят адрес current-> next, хотя я освободил current, а мой VC ++ - нет. Я просто хочу понять, как работают компиляторы.

Ответы [ 6 ]

18 голосов
/ 14 апреля 2010

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

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

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

11 голосов
/ 14 апреля 2010

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

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

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

4 голосов
/ 14 апреля 2010

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

3 голосов
/ 14 апреля 2010

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

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

1 голос
/ 14 апреля 2010

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

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

0 голосов
/ 14 апреля 2010

Это потому, что компилятор поместил инструкции в двоичный файл, которые запоминают адрес 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'

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

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

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