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

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

Это код C, который освобождает память отдельного списка. Он скомпилирован с Visual С++ 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;
}

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

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'

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

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