Я запускаю код из листинга 17.2, P781, C Primer Plus 6th Edition.
/* films2.c -- using a linked list of structures */
#include <stdio.h>
#include <stdlib.h> /* has the malloc prototype */
#include <string.h> /* has the strcpy prototype */
#define TSIZE 45 /* size of array to hold title */
struct film {
char title[TSIZE];
int rating;
struct film *next; /* points to next struct in list */
};
char *s_gets(char *st, int n);
int main(void)
{
struct film *head = NULL;
struct film *prev, *current;
char input[TSIZE];
/* Gather and store information */
puts("Enter first movie title:");
while (s_gets(input, TSIZE) != NULL && input[0] != '\0') {
current = (struct film *) malloc(sizeof(struct film));
if (head == NULL) /* first structure */
head = current;
else /* subseqent structures */
prev->next = current;
current->next = NULL;
strcpy(current->title, input);
puts("Enter your rating <0-10>:");
scanf("%d", ¤t->rating);
while (getchar() != '\n')
continue;
puts("Enter next movie title (empty line to stop):");
prev = current;
}
/* show list of movies */
if (head == NULL)
printf("No data entered.");
else
printf("Here is the movie list:\n");
current = head;
while (current != NULL) {
printf("Movie: %s Rating: %d\n",
current->title, current->rating);
current = current->next;
}
/* Program done, so free allocated memory */
current = head;
while (current != NULL) {
free(current);
printf("Movie: %s, Rating: %d\n",
current->title, current->rating);
current = current->next;
}
printf("Bye!\n");
return 0;
}
char *s_gets(char *st, int n)
{
char *ret_val;
char *find;
ret_val = fgets(st, n, stdin);
if (ret_val) {
find = strchr(st, '\n'); // look for newline
if (find) // if the address is not NULL,
*find = '\0'; // place a null character there
else
while (getchar() != '\n')
continue; // dispose of rest of line
}
return ret_val;
}
Мой вопрос в том, что после free(current)
, current->title
и current->rating
все еще есть, а current->next
нет NULL
. Я не знаю почему.
О, это код из книги? Выброси это.
да книга неправильная. После звонка free
вы никогда больше не сможете прикасаться к этому воспоминанию. Если вы это сделаете, компьютер может взорваться. Большинство не будет, но Deathstation 9000, безусловно, будет.
Эта книга была первоначально опубликована в 1984 году, и хотя в то время она, возможно, была хорошим справочником, сейчас она ужасно устарела, если этот код все еще присутствует в ней. Обычный метод написания неряшливого кода на C "не беспокойтесь, все в порядке" больше не работает.
@tadman: 6-е издание было опубликовано в 2013 году. Если этот код находится в 6-м издании, об этом необходимо сообщить как об ошибке. Ни gcc, ни clang не обнаруживают ошибку, поэтому неудивительно, что слишком не была обнаружена, но valgrind ее ловит.
@ЕвгенийШ. Почему функция printf
может печатать правильные значения после того, как current
указывает на недопустимое местоположение?
@KeithThompson Может быть, эта книга отличается, но многие старые книги получают очень незначительные обновления, которые никогда не решают такие серьезные проблемы, как эта, только опечатки и тому подобное.
@KeithThompson@tadman Прошу прощения за свою ошибку. Оригинальный код содержит функцию printf
. Я обнаружил, что цикл для освобождения памяти странный, поэтому я добавил функцию printf
, чтобы определить, работает ли функция освобождения. Однако после добавления он все еще работает. Меня смущает функция free
. Как работает free
? Какую память освобождает функция free
?
@mathsyouth: free
делает память доступной для дальнейшего распределения. Это не (обязательно) делает невозможным доступ к нему. Доступ к освобожденной памяти имеет неопределенное поведение. Вам, программисту, решать, как этого избежать; реализация, вероятно, не поможет вам.
По крайней мере, эта петля неверна
current = head;
while (current != NULL) {
free(current);
printf("Movie: %s, Rating: %d\n",
current->title, current->rating);
current = current->next;
}
там должен быть
current = head;
while (current != NULL) {
printf("Movie: %s, Rating: %d\n",
current->title, current->rating);
struct film *tmp = current;
current = current->next;
free(tmp);
}
В противном случае вы пытаетесь использовать недопустимый удаленный указатель.
Use-after-free — довольно вопиющая ошибка, особенно при обучении.
Спасибо за вашу точку зрения. Однако я не понимаю, почему он все еще работает, когда я пытаюсь использовать недопустимый удаленный указатель. «работает» означает, что функция по-прежнему печатает значения переменных.
@mathsyouth: Такова природа неопределенного поведения. Что может случиться с худший, так это то, что код "работает".
Переменная по-прежнему доступна для использования, но выделенного пространства нет, поэтому у вас будет неопределенное поведение. Это может работать сейчас, но может рухнуть позже. Хорошей практикой является установка указателя на NULL после его освобождения.
Код неверен, как указано в других ответах.
На одной из последующих страниц автор показывает правильную функцию освобождения связанного списка. Он говорит
The code saves the address of the next node because the call to
free()
, in principle, may make the contents of the current node (the one pointed to by*plist
) no longer available.
Хотя технически это правильно, этот отрывок лишает автора права преподавателя C. Вот исправленный отрывок.
The code saves the address of the next node because the call to free() makes the contents of the current node (the one pointed to by
*plist
) no longer available, and accessing it is undefined behaviour.
По-видимому, автор никогда не упоминает термин «неопределенное поведение». Это неприемлемо в любом серьезном учебнике C.
Освобождение
current
не делает егоNULL
. Он просто не может изменить его, так как он передается по значению. Он просто указывает на недопустимое местоположение. Разыменование это UB.