Ошибка сегментации при печати связанного списка после чтения из файла - PullRequest
0 голосов
/ 08 октября 2018

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

Программа позволит пользователям добавлять данные пациента, а затем сохранять данные в базе данных текстовых файлов.Затем пользователи могут выйти из программы, запустить программу и загрузить базу данных текстовых файлов.Наконец, пользователи смогут видеть информацию о пациенте в программе.

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

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

#define DB_NAME "database"

struct dob
{
int day, month, year;
};
typedef struct dob dob_t;

struct medicine
{
int medicine_id;
char medicine_name[100];
};
typedef struct medicine medicine_t;

struct patient
{
    int patient_id;
    dob_t date_db;
    char patient_name[20];
    medicine_t patient_med;
    struct patient* nextp; 

};
typedef struct patient patient_t;

void print_menu (void);
patient_t* add_patients (patient_t* patient_headp, patient_t* temp, patient_t* patient_currentp, int num_patients);
void view_patients (patient_t* patient_currentp, patient_t* patient_headp);
void save_patients (patient_t* patient_currentp, patient_t* patient_headp);
patient_t* read_patients (patient_t* patient_currentp, patient_t* patient_headp, patient_t* temp);


int main (void){
patient_t* patient_headp = NULL;
patient_t* temp = NULL;
patient_t* patient_currentp = NULL;
int option_picked = 0;
int num_patients = 0;
while(option_picked != 5)
{
    print_menu ();
    scanf("%d", &option_picked);
    if (option_picked == 1){
        patient_headp = add_patients(patient_headp, temp, patient_currentp, num_patients);
    }


    else if (option_picked == 2){
        view_patients (patient_currentp, patient_headp);
    }


    else if (option_picked == 3){
        save_patients (patient_currentp, patient_headp);
    }


    else if (option_picked == 4){
        patient_headp = read_patients (patient_currentp, patient_headp, temp);
    }
}   
return 0;
}

void print_menu (void)
{
printf("\n"
"1. add a patient\n"
"2. display all patients\n"
"3. save the patients to the database file\n"
"4. load the patients from the database file\n"
"5. exit the program\n"
"Enter choice (number between 1-5)>\n");
}

patient_t* add_patients (patient_t* patient_headp, patient_t* temp, patient_t* patient_currentp, int num_patients){
char choice;
do
{
    temp = (patient_t*) malloc(sizeof(patient_t));
    if (temp == NULL){
        printf("Error allocating memory\n");
    }
    printf("Enter Patient ID: ");
    scanf("%d", &temp->patient_id);
    printf("Enter Patient DOB(DD MM YY): ");
    scanf("%d %d %d", &temp->date_db.day, &temp->date_db.month, 
    &temp->date_db.year);
    printf("Enter Patient Name: ");
    scanf("%s", temp->patient_name);
    printf("Enter Patient Medicine Prescription: ");
    scanf("%s", temp->patient_med.medicine_name);
    printf("Enter Patient Medicine Prescription ID: ");
    scanf("%d", &temp->patient_med.medicine_id);
    temp->nextp = NULL;
    if(patient_headp == NULL){
        patient_headp = temp;
    }
    else{
        patient_currentp = patient_headp;
        while(patient_currentp->nextp != NULL){
            patient_currentp = patient_currentp->nextp;
        }
        patient_currentp->nextp = temp;
    }
    printf("Add more patients? (Y/N) ");
    scanf(" %c", &choice);
    num_patients++;
} 
while (choice == 'Y');
return patient_headp;
}


void view_patients (patient_t* patient_currentp, patient_t* patient_headp){
    /*patient_currentp = (patient_t*) malloc(sizeof(patient_t));
    if (patient_currentp == NULL){
        printf("Error allocating memory\n");
    }
    patient_currentp = patient_headp;
    do{
        printf("%05d %02d/%02d/%02d %s %s %d\n", patient_currentp->patient_id, 
        patient_currentp->date_db.day, patient_currentp->date_db.month, 
        patient_currentp->date_db.year, patient_currentp->patient_name, 
        patient_currentp->patient_med.medicine_name, 
        patient_currentp->patient_med.medicine_id);
        patient_currentp = patient_currentp->nextp;
    }while(patient_currentp->nextp != NULL);*/
    printf("%05d %02d/%02d/%02d %s %s %d\n", patient_headp->patient_id, 
        patient_headp->date_db.day, patient_headp->date_db.month, 
        patient_headp->date_db.year, patient_headp->patient_name, 
        patient_headp->patient_med.medicine_name, 
        patient_headp->patient_med.medicine_id);
}


void save_patients (patient_t* patient_currentp, patient_t* patient_headp){
    FILE *output = fopen(DB_NAME, "a");
    if (output == NULL){
        printf("Failed to open file\n");
    }
    patient_currentp = patient_headp;
    do{
        fprintf(output, "%05d %02d/%02d/%02d %s %s %d\n", patient_currentp->patient_id, 
        patient_currentp->date_db.day, patient_currentp->date_db.month, 
        patient_currentp->date_db.year, patient_currentp->patient_name, 
        patient_currentp->patient_med.medicine_name, 
        patient_currentp->patient_med.medicine_id);

        patient_currentp = patient_currentp->nextp;
    }while(patient_currentp != NULL);
    fclose(output);
}


patient_t* read_patients (patient_t* patient_currentp, patient_t* patient_headp, patient_t* temp){
    FILE *input = fopen(DB_NAME, "r");
    if (input == NULL){
        printf("Failed to open file\n");
    }
    do{
        temp = (patient_t*) malloc(sizeof(patient_t));
        if (temp == NULL){
            printf("Error allocating memory\n");
        }
        while ((fscanf(input, "%05d %02d/%02d/%02d %s %s %d", 
        &temp->patient_id, &temp->date_db.day, 
        &temp->date_db.month, &temp->date_db.year, 
        temp->patient_name, 
        temp->patient_med.medicine_name, 
        &temp->patient_med.medicine_id)) != EOF)

        printf("%05d %02d/%02d/%02d %s %s %d\n", temp->patient_id, 
        temp->date_db.day, temp->date_db.month, 
        temp->date_db.year, temp->patient_name, 
        temp->patient_med.medicine_name, 
        temp->patient_med.medicine_id);

        temp->nextp = NULL;
        if(patient_headp == NULL){
            patient_headp = temp;
        }
        else{
            patient_currentp = patient_headp;
            while(patient_currentp->nextp != NULL){
                patient_currentp = patient_currentp->nextp;
            }
            patient_currentp->nextp = temp;
        }
    }while(patient_currentp != NULL);
return patient_headp;
}

Ответы [ 2 ]

0 голосов
/ 08 октября 2018

Реальность вашей проблемы состоит в том, что (1) без проверки входных данных существует так много потенциальных источников ошибок, которые могут привести к неопределенному поведению и ошибке сегментации, которую трудно определить (2) любойкогда адрес списка может измениться в функции (например, изменяется первый узел), вам нужно передать адрес patient_headp, чтобы функция получила фактический указатель списка вместо копии указателяудержание адреса списка, и (3) ваш read_patients() не функционирует (по ряду причин), но в основном потому, что установка patient_currentp = patient_currentp->nextp; гарантирует while(patient_currentp != NULL); тест false.

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

Прежде чем мы даже посмотрим на код, при вводе пользовательского ввода с помощью scanf возникают ловушки для неосторожных в случае совпадения или вход сбой.Чтобы даже начать использовать scanf правильно, вы должны подтверждать возврат каждый раз.Это означает обработку EOF, совпадение или вход сбой и обработку допустимого случая ввода.Как минимум, вы должны проверить, что ожидаемое количество конверсий имело место перед использованием ввода.

В случае ошибки совпадения извлечение символа из входного буфера прекращается, оставляя непрочитанный символ непрочитанным, ожидая, что вы укусите вас при следующей попытке чтения.Чтобы облегчить восстановление после ошибки , совпадающей с , вы должны удалить поврежденные символы из входного буфера.Обычный подход для stdin - это просто чтение с getchar(), пока не встретится '\n' или EOF.Короткая вспомогательная функция облегчает жизнь, например,

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

В дополнение к проблеме проверки, вы можете найти более надежным указание успеха / неудачи в вашей функции add_patients(), возвращая указатель на узелдобавлено (или NULL при сбое).Это несколько усложняется тем, что вы зацикливаетесь внутри функции для добавления нескольких пациентов, а не просто снова вызываете функцию из меню.В любом случае, возврат указателя на последний добавленный узел работает одинаково хорошо.

Невозможно пройтись по каждой проблеме в вашем коде с помощью символов, выделенных для этого ответа.Вместо этого я привел в порядок ваш код таким образом, чтобы он обращался к каждой проверке пользовательского ввода, удалял ненужные параметры из объявлений функций и изменил типы возвращаемых значений для save_patients() и read_patients() на int, чтобы обеспечить 1при успешной записи или чтении, 0 в противном случае.

( note проверка fclose в save_patients(). Каждый раз, когда вы записываете в файл, вы должны проверить fclose, так как он перехватитпотоковые ошибки, а также ошибки с вашей последней записью, которые не могли быть в состоянии сообщить до закрытия)

Код соответствует вашему подходу, он только что был усовершенствован в нескольких местах.Просмотрите это:

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

#define DB_NAME "database"
#define MAXRX   100         /* if you need constants, #define on (or more) */
#define MAXNM    20         /*  (don't use "magic numbers" in your code )  */

typedef struct {
    int day, month, year;
} dob_t;

typedef struct {
    int medicine_id;
    char medicine_name[MAXRX];
} medicine_t;

typedef struct patient {
    int patient_id;
    dob_t date_db;
    char patient_name[MAXNM];
    medicine_t patient_med;
    struct patient* nextp; 
} patient_t;

void empty_stdin (void)
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

void print_menu (void);
patient_t *add_patients (patient_t **patient_headp, int *num_patients);
void view_patients (patient_t *patient_headp);
int save_patients (patient_t *patient_headp);
int read_patients (patient_t **patient_headp, int *num_patients);


int main (void) {

    patient_t   *patient_headp = NULL;
    int option_picked = 0,
        num_patients = 0;

    while(option_picked != 5)
    {
        print_menu ();
        if (scanf("%d", &option_picked) != 1) { /* VALIDATE EVERY USER INPUT */
            fputs ("\n  error: invalid input.\n", stderr);
            empty_stdin();
            continue;
        }

        if (option_picked == 1)
            add_patients (&patient_headp, &num_patients);
        else if (option_picked == 2)
            view_patients (patient_headp);
        else if (option_picked == 3)
            save_patients (patient_headp);
        else if (option_picked == 4)
            read_patients (&patient_headp, &num_patients);
    }   
    return 0;
}

void print_menu (void)
{
    printf ("\n"
            "1. add a patient\n"
            "2. display all patients\n"
            "3. save the patients to the database file\n"
            "4. load the patients from the database file\n"
            "5. exit the program\n\n"
            "Enter choice (number between 1-5)> ");
}

patient_t *add_patients (patient_t **patient_headp, int *num_patients)
{
    patient_t   *patient_currentp = *patient_headp,
                *temp = NULL;
    char choice = 0;

    do
    {
        temp = malloc (sizeof *temp);           /* allocate */
        if (temp == NULL){                      /* validate */
            perror ("add_patients-malloc");
            return NULL;
        }
        temp->nextp = NULL;                     /* initialize */

        printf ("Enter Patient ID: ");
        if (scanf ("%d", &temp->patient_id) != 1)
            goto error_add_pt;

        printf ("Enter Patient DOB(DD MM YY): ");
        if (scanf ("%d %d %d", &temp->date_db.day, &temp->date_db.month, 
                            &temp->date_db.year) != 3)
            goto error_add_pt;

        printf ("Enter Patient Name: ");
        if (scanf ("%s", temp->patient_name) != 1)
            goto error_add_pt;

        printf ("Enter Patient Medicine Prescription: ");
        if (scanf ("%s", temp->patient_med.medicine_name) != 1)
            goto error_add_pt;

        printf ("Enter Patient Medicine Prescription ID: ");
        if (scanf ("%d", &temp->patient_med.medicine_id) != 1)
            goto error_add_pt;

        if (*patient_headp == NULL){
            *patient_headp = patient_currentp = temp;
        }
        else {
            while (patient_currentp->nextp != NULL){
                patient_currentp = patient_currentp->nextp;
            }
            patient_currentp->nextp = temp;
        }
        (*num_patients)++;

        printf ("Add more patients? (Y/N) ");
        if (scanf (" %c", &choice) < 1) {
            fputs (" user canceled input.\n", stderr);
            break;
        }
    } 
    while (choice == 'Y' || choice == 'y');

    return temp;    /* return pointer to most recent node added */

  error_add_pt:;
    fputs ("error: invalid input\n", stderr);
    empty_stdin();
    free (temp);
    return NULL;
}


void view_patients (patient_t *patient_headp)
{
    patient_t *patient_currentp = patient_headp;

    while (patient_currentp != NULL) {
        printf ("%05d %02d/%02d/%02d %s %s %d\n", patient_currentp->patient_id, 
                patient_currentp->date_db.day, patient_currentp->date_db.month, 
                patient_currentp->date_db.year, patient_currentp->patient_name, 
                patient_currentp->patient_med.medicine_name, 
                patient_currentp->patient_med.medicine_id);

        patient_currentp = patient_currentp->nextp;
    }   
}


int save_patients (patient_t *patient_headp)
{
    patient_t *patient_currentp = patient_headp;
    FILE *output = fopen(DB_NAME, "a");

    if (output == NULL) {   /* validate file open to append */
        fprintf (stderr, "error: file open failed '%s'\n", DB_NAME);
        return 0;
    }

    while(patient_currentp != NULL) {

        fprintf (output, "%05d %02d/%02d/%02d %s %s %d\n", 
                patient_currentp->patient_id, 
                patient_currentp->date_db.day, patient_currentp->date_db.month, 
                patient_currentp->date_db.year, patient_currentp->patient_name, 
                patient_currentp->patient_med.medicine_name, 
                patient_currentp->patient_med.medicine_id);

        patient_currentp = patient_currentp->nextp;
    }

    if (fclose (output) == EOF) {
        fputs ("error: stream error on fclose.\n", stderr);
        return 0;
    }

    return 1;
}

int read_patients (patient_t **patient_headp, int *num_patients)
{
    patient_t   tmp = {0},
                *patient_currentp = *patient_headp;
    FILE *input = fopen(DB_NAME, "r");

    if (input == NULL){ /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'\n", DB_NAME);
        return 0;
    }

    while (patient_currentp && patient_currentp->nextp != NULL)
        patient_currentp = patient_currentp->nextp;

    while (fscanf (input, "%05d %02d/%02d/%02d %19s %99s %d", 
                &tmp.patient_id, &tmp.date_db.day, 
                &tmp.date_db.month, 
                &tmp.date_db.year, 
                tmp.patient_name, 
                tmp.patient_med.medicine_name, 
                &tmp.patient_med.medicine_id) == 7) {

        patient_t *node = malloc (sizeof *node);
        if (node == NULL) {
            perror ("read_patients-malloc");
            return 0;
        }
        node->nextp = NULL;

        *node = tmp;

        if (!patient_currentp)
            *patient_headp = patient_currentp = node;
        else {
            patient_currentp->nextp = node;
            patient_currentp = patient_currentp->nextp;
        }
        (*num_patients)++;

        printf ("%05d %02d/%02d/%02d %s %s %d\n", node->patient_id, 
                node->date_db.day, node->date_db.month, 
                node->date_db.year, node->patient_name, 
                node->patient_med.medicine_name, 
                node->patient_med.medicine_id);
    }

    fclose (input);

    return 1;
}

( note: при повторном выделении patient_currentp в read_patients() произошла утечка памяти и произошла перезапись значений указателей, которые вы ранее присвоили своему списку.поэтому используется дополнительная переменная node)

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

$ ./bin/llpatients

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 1
Enter Patient ID: 10001
Enter Patient DOB(DD MM YY): 1 1 72
Enter Patient Name: Epoch
Enter Patient Medicine Prescription: Clonapin
Enter Patient Medicine Prescription ID: 2001
Add more patients? (Y/N) y
Enter Patient ID: 10002
Enter Patient DOB(DD MM YY): 31 10 72
Enter Patient Name: Halloween
Enter Patient Medicine Prescription: Potion
Enter Patient Medicine Prescription ID: 2002
Add more patients? (Y/N) n

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 2
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 3

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 5

Пример использования / вывод - чтение из файла

$ ./bin/llpatients

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 4
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 2
10001 01/01/72 Epoch Clonapin 2001
10002 31/10/72 Halloween Potion 2002

1. add a patient
2. display all patients
3. save the patients to the database file
4. load the patients from the database file
5. exit the program

Enter choice (number between 1-5)> 5

Снова посмотрите, поймите, почему были сделаны изменения, и спросите, есть ли у вас какие-либо дополнительные вопросы.

0 голосов
/ 08 октября 2018

in add_patients происходит следующее

Создать узел

/* Create a temporary node */
temp = (patient_t*) malloc(sizeof(patient_t));

Заполнить узел данными

/* Copy Data to temporary Node */
printf("Enter Patient ID: ");
..
..
temp->nextp = NULL;

Изменения связанного списка

if(patient_headp == NULL){
    patient_headp = temp;
}
else{
    patient_currentp = patient_headp;
    while(patient_currentp->nextp != NULL){
        patient_currentp = patient_currentp->nextp;
    }
    patient_currentp->nextp = temp;
}

в этот момент temp все еще указывает на выделенную память, как и linked list

, а затем это

free(temp);

приводит к segfault.поэтому удалите free

...