Связанный список не принимает следующий элемент (язык C)

Когда вы объявляете ссылочную переменную (т. е. объект), вы действительно создаете указатель на объект. Рассмотрим следующий код, в котором вы объявляете переменную примитивного типа int:

int x;
x = 10;

В этом примере переменная x является int, и Java инициализирует ее для 0. Когда вы назначаете его 10 во второй строке, ваше значение 10 записывается в ячейку памяти, на которую указывает x.

Но когда вы пытаетесь объявить ссылочный тип, произойдет что-то другое. Возьмите следующий код:

Integer num;
num = new Integer(10);

Первая строка объявляет переменную с именем num, но она не содержит примитивного значения. Вместо этого он содержит указатель (потому что тип Integer является ссылочным типом). Поскольку вы еще не указали, что указать на Java, он устанавливает значение null, что означает «Я ничего не указываю».

Во второй строке ключевое слово new используется для создания экземпляра (или создания ) объекту типа Integer и переменной указателя num присваивается этот объект. Теперь вы можете ссылаться на объект, используя оператор разыменования . (точка).

Exception, о котором вы просили, возникает, когда вы объявляете переменную, но не создавали объект. Если вы попытаетесь разыменовать num. Перед созданием объекта вы получите NullPointerException. В самых тривиальных случаях компилятор поймает проблему и сообщит вам, что «num не может быть инициализирован», но иногда вы пишете код, который непосредственно не создает объект.

Например, вы можете имеют следующий метод:

public void doSomething(SomeObject obj) {
   //do something to obj
}

В этом случае вы не создаете объект obj, скорее предполагая, что он был создан до вызова метода doSomething. К сожалению, этот метод можно вызвать следующим образом:

doSomething(null);

В этом случае obj имеет значение null. Если метод предназначен для того, чтобы что-то сделать для переданного объекта, целесообразно бросить NullPointerException, потому что это ошибка программиста, и программисту понадобится эта информация для целей отладки.

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

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Наконец, Как определить исключение & amp; причина использования Трассировки стека

0
задан user1885868 14 April 2019 в 08:45
поделиться

2 ответа

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

Обратите внимание, что «элемент» - это переменная типа указателя, поэтому он имеет размер указателя (вероятно, 8 байт), в то время как «Элемент» - это тип вашей структуры, размер которого намного больше. Таким образом, когда вы выделяете только sizeof(element) байтов, это слишком мало.

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

0
ответ дан Elias 14 April 2019 в 08:45
поделиться

Хотя у вас уже есть ответ для вашего SegFault, существуют дополнительные области, в которых вы можете очистить и реорганизовать свой код для более эффективной совместной работы. Поскольку вы используете структуру списка liste для хранения указателя на начало списка в first, вы также можете добавить еще один указатель last, чтобы указать на последний узел в списке, и избавиться от необходимости повторяться в последний узел на каждой вставке. С указателем last (или tail) ваш новый узел всегда вставляется в last->next. Например, ваша структура Liste может иметь вид:

typedef struct Liste Liste;

struct Liste {
    Element *first, *last;
};

Ваши функции списка должны выполнять одну вещь, то есть initialize() должна просто выделять и инициализировать узел Liste и его указатели. read() должен выделять, читать и возвращать действительный указатель на заполненный узел или NULL в случае сбоя. insert() должен сделать именно это, взять адрес списка Liste и узел из read() и вставить его в список. Соединяя эти функции вместе, вы можете сделать:

Element *read()
{
    Element *element = malloc (sizeof(*element));   /* allocate */
    if (element == NULL)                            /* validate */
        return NULL;
    element->next = NULL;                           /* initialize */

    printf ("\nPlease provide first name : ");
    if (scanf ("%9s", element->f_name) != 1)   /* validate EVERY input */
        goto badread;

    printf ("Please provide last name  : ");
    if (scanf ("%9s", element->l_name) != 1)
        goto badread;

    printf ("Please provide score      : ");
    if (scanf ("%f", &element->score) != 1)
        goto badread;

    return element;     /* return allocated and initialized element */

badread:;     /* just a simple goto label for handling read error */

    free (element);     /* free memory of node if error */

    return NULL;
}

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

/* initialize the list, don't worry about the elements */
Liste *initialize (void)
{
    Liste *liste = malloc(sizeof *liste);
    if (liste == NULL) {
        perror ("malloc-liste");    /* give some meaningful error */
        exit (EXIT_FAILURE);
    }
    liste->first = liste->last = NULL;

    return liste;
}

void insert (Liste *liste, Element *nouveau)
{
    if (liste == NULL || nouveau == NULL)
        exit (EXIT_FAILURE);

    if (!liste->first)                          /* inserting 1st node */
        liste->first = liste->last = nouveau;
    else {                                      /* inserting all others */
        liste->last->next = nouveau;
        liste->last = nouveau;
    }
}

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

В целом, вы можете написать свой полный тестовый код следующим образом, добавив функцию для перебора значений печати списка, а затем аналогичную функцию для перебора узлов освобождения списка и, наконец, список ::

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

#define MAXN 10     /* if you need a constant, #define one (or more) */

typedef struct Element Element;

struct Element {
    char f_name[MAXN];
    char l_name[MAXN];
    float score;
    Element* next;
};

typedef struct Liste Liste;

struct Liste {
    Element *first, *last;
};

Element *read()
{
    Element *element = malloc (sizeof(*element));   /* allocate */
    if (element == NULL)                            /* validate */
        return NULL;
    element->next = NULL;                           /* initialize */

    printf ("\nPlease provide first name : ");
    if (scanf ("%9s", element->f_name) != 1)   /* validate EVERY input */
        goto badread;

    printf ("Please provide last name  : ");
    if (scanf ("%9s", element->l_name) != 1)
        goto badread;

    printf ("Please provide score      : ");
    if (scanf ("%f", &element->score) != 1)
        goto badread;

    return element;     /* return allocated and initialized element */

badread:;     /* just a simple goto label for handling read error */

    free (element);     /* free memory of node if error */

    return NULL;
}

/* initialize the list, don't worry about the elements */
Liste *initialize (void)
{
    Liste *liste = malloc(sizeof *liste);
    if (liste == NULL) {
        perror ("malloc-liste");    /* give some meaningful error */
        exit (EXIT_FAILURE);
    }
    liste->first = liste->last = NULL;

    return liste;
}

void insert (Liste *liste, Element *nouveau)
{
    if (liste == NULL || nouveau == NULL)
        exit (EXIT_FAILURE);

    if (!liste->first)                          /* inserting 1st node */
        liste->first = liste->last = nouveau;
    else {                                      /* inserting all others */
        liste->last->next = nouveau;
        liste->last = nouveau;
    }
}

void prnlist (Liste *liste)
{
    Element *iter = liste->first;

    while (iter) {  /* just iterate list outputting values */
        printf ("%-10s %-10s  ->  %.2f\n", 
                iter->f_name, iter->l_name, iter->score);
        iter = iter->next;
    }
}

void freelist (Liste *liste)
{
    Element *iter = liste->first;

    while (iter) {
        Element *victim = iter;
        iter = iter->next;          /* iterate to next node BEFORE */
        free (victim);              /* you free victim */
    }
    free (liste);
}

int main (void) {

    Liste *maListe = initialize();  /* create/initialize list */
    Element *node;

    while ((node = read()))         /* allocate/read */
        insert (maListe, node);     /* insert */

    puts ("\n\nElements in list:\n");   /* output list values */
    prnlist (maListe);

    freelist (maListe);     /* don't forget to free what you allocate */

    return 0;
}

Пример использования / вывода

$ ./bin/ll_liste

Please provide first name : Donald
Please provide last name  : Duck
Please provide score      : 99.2

Please provide first name : Minnie
Please provide last name  : Mouse
Please provide score      : 99.7

Please provide first name : Pluto
Please provide last name  : Dog
Please provide score      : 83.5

Please provide first name :

Elements in list:

Donald     Duck        ->  99.20
Minnie     Mouse       ->  99.70
Pluto      Dog         ->  83.50

Использование памяти / проверка ошибок

[ 1131] В любом написанном вами коде, который динамически выделяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока память так, (2) он может быть освобожден , когда он больше не нужен.

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

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

$ valgrind ./bin/ll_liste
==10838== Memcheck, a memory error detector
==10838== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10838== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==10838== Command: ./bin/ll_liste
==10838==

Please provide first name : Donald
Please provide last name  : Duck
Please provide score      : 99.2

Please provide first name : Minnie
Please provide last name  : Mouse
Please provide score      : 99.6

Please provide first name : Pluto
Please provide last name  : Dog
Please provide score      : 87.2

Please provide first name :

Elements in list:

Donald     Duck        ->  99.20
Minnie     Mouse       ->  99.60
Pluto      Dog         ->  87.20
==10838==
==10838== HEAP SUMMARY:
==10838==     in use at exit: 0 bytes in 0 blocks
==10838==   total heap usage: 5 allocs, 5 frees, 144 bytes allocated
==10838==
==10838== All heap blocks were freed -- no leaks are possible
==10838==
==10838== For counts of detected and suppressed errors, rerun with: -v
==10838== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Посмотрите вещи и дайте мне знать, если у вас есть вопросы.

0
ответ дан David C. Rankin 14 April 2019 в 08:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: