Почему эта программа связанного списка C выдает «ошибку сегментации»?

Первая функция читает файл с кучей символов и помещает их в связанный список. Это не работает :(.

#include <stdio.h>
#include <stdlib.h>

struct list {
    char val;
    struct list* next;
};

typedef struct list element;

int lcreate(char* fname, element* list);
int ldelete(element* list);
int linsert(char a, char b, element* list);
int lremove(char a, element* list);
int lsave(char* fname, element* list);



int lcreate(char* fname, element* list) {
    element* elem = list;
    char c = 0;
    FILE * file = NULL;

    file = fopen(fname, "r");

    while ((c = getc(file)) != EOF)
    {
        if(list == NULL) {
            list = (element*)malloc(sizeof(element));
            if(list == NULL) {
                return 0;
            }
            list->val = c;
        }
        else {

            elem->next=(element*)malloc(sizeof(element));
            elem = elem->next;
            elem-> val = c;
        }
    }
    fclose(file);
    elem->next = NULL;
    return 1;
}



int main(void) {
    int i = 0;


    element * list = NULL;
    lcreate("list.txt", list);

    for(i = 0; i<4; ++i) {
        printf("%c", list->val);
        list = list->next;
    }

    return 0;
}

Исправлена проблема с нулевым значением «файла».


person ron    schedule 18.02.2010    source источник


Ответы (6)


Одна очевидная проблема прямо здесь:

FILE * file = NULL;

fopen(fname, "r");

Чтобы fopen многого добился, вам нужно присвоить результат от fopen вашему FILE *:

file = fopen(fname, "r");

Изменить: поскольку вы работаете на C, вы не можете передать указатель по ссылке. В качестве альтернативы вы можете передать указатель на указатель:

int lcreate(char *fname, element **list) {

     // ...
     *list = malloc(sizeof(element));
     (*list)->next = null;
     (*list)->val = c;
// ...
}

По сути, весь код внутри lcreate должен ссылаться на *list, а не только на list. В качестве альтернативы вы можете взять указатель на существующий список в качестве входных данных и вернуть указатель на список, поэтому в main у вас будет что-то вроде: list = lcreate("list.txt", list);

person Jerry Coffin    schedule 18.02.2010
comment
И не забывайте, что elem= list присваивает значение. Если вы хотите, чтобы elem обновлялось при обновлении list, вам нужно изменить elem на element **elem= &list в исходном коде или element **elem= list в версии Джерри. - person MSN; 19.02.2010

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

person Anon.    schedule 18.02.2010

В вашей функции main вы также передаете list по значению в lcreate. В функции lcreate() вы перезаписываете локальную копию list, не изменяя значение list в основной функции. Поскольку list инициализируется как NULL, при вызове list->val вы получите segfault.

person mob    schedule 18.02.2010
comment
Итак, как мне перезаписать список в lcreate? Спасибо! - person ron; 19.02.2010
comment
@ron - передать двойной указатель на list, то есть element** list; lcreate("list.txt", list);. Затем в методе lcreate вы выделяете его следующим образом: *list = malloc(...). Это гарантирует, что вызывающая сторона увидит изменения, внесенные вызываемой стороной. - person LeopardSkinPillBoxHat; 19.02.2010

Да, то, что другие говорили об указателе FILE и передаче list по значению, а не по ссылке на lcreate(), верно.

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

Вы пытаетесь выполнить итерацию по списку 4 раза в функции main(), но в списке может быть меньше 4 элементов. В конце концов, printf() вызовет ошибку сегментации, если list равно NULL.

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

Обновление:

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

person LeopardSkinPillBoxHat    schedule 18.02.2010

Я также вижу дополнительную проблему. В операторе while оператора lcreate() истинное предложение оператора if malloc выделяет некоторую память и назначает ее list, однако elem не обновляется.

while ((c = getc(file)) != EOF)
{
    if(list == NULL) {
        list = (element*)malloc(sizeof(element));
        if(list == NULL) {
            return 0;
        }
        list->val = c;
    }
    else {

В следующий раз в цикле while list не будет ненулевым, но elem по-прежнему будет нулевым, поэтому присваивание elem->next пытается уважать нулевой указатель и, следовательно, ошибку сегментации (что, кстати, означает, что вы пытались получить доступ к памяти, которая не был назначен вашему процессу): -

else {
    elem->next=(element*)malloc(sizeof(element));

Как указывали другие, вы также не возвращаете list обратно в main, поэтому он все равно будет NULL, когда вы нажмете цикл printf().

Наконец, отладчик — ваш друг при рассмотрении этих проблем. Вы увидите, какая именно строка вызывает ошибку seg и в каком состоянии были переменные.

person Andrew O'Reilly    schedule 18.02.2010

Было бы хорошо проверить, был ли malloc успешным, проверив ненулевой пинтер. Кроме того, вы можете захотеть выделить ссылку head/first за пределы while, чтобы избежать нулевой проверки для головы каждый раз в цикле while. Конечно, это оптимизация на случай, если ваш связанный список станет очень большим!

person Swapna    schedule 19.02.2010