首页 > 解决方案 > 从文件读取后打印链表的分段错误

问题描述

我几乎完成了添加、查看、保存和加载患者详细信息的 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;
}

标签: clinked-listsegmentation-faultprintf

解决方案


您的问题的实际情况是(1)没有输入验证,有很多潜在的错误来源可能导致未定义的行为和分段错误,以至于很难确定(2)任何时候列表的地址都可以更改一个函数(例如第一个节点更改),您需要传递的地址, patient_headp以便该函数接收实际的列表指针,而不是保存列表地址的指针的副本,并且(3)您read_patients()是非功能性的(对于一个数字原因),但基本上是因为设置patient_currentp = patient_currentp->nextp;保证while(patient_currentp != NULL);测试错误。

没有理由patient_currentp作为参数传递。没有理由传递num_patients其当前形式,该参数未使用,并且要使其有用,您需要传递一个指针,num_patients以便您可以在添加和读取函数中更新它,并使更新后的计数返回到调用函数。

甚至在我们查看代码之前,在匹配输入失败scanf的情况下,如果粗心大意地接受用户输入,就会有陷阱。要开始正确使用,您必须每次都验证退货。这意味着处理,匹配输入失败,并处理有效的输入案例。至少,您必须在使用输入之前检查是否发生了预期的转换次数。scanfEOF

匹配失败的情况下,从输入缓冲区中提取字符会停止,让有问题的字符未读,等待下次尝试读取时咬你。为了便于从匹配失败中恢复,您可以从输入缓冲区中删除违规字符。正常的方法stdin是简单地阅读getchar()直到'\n'EOF遇到。一个简短的辅助功能使生活更轻松,例如

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

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

除了验证问题之外,您可能会发现add_patients()通过返回指向添加的节点(或NULL失败时)的指针来指示函数中的成功/失败更加健壮。这有点复杂,因为您在函数中循环添加多个患者,而不是简单地从菜单中再次调用该函数。无论如何,返回指向添加的最后一个节点的指针同样有效。

无法以分配给此答案的字符来逐步解决代码中的每个问题。相反,我整理了您的代码,以解决每个用户输入验证问题,从您的函数声明中删除不必要的参数,并更改返回类型 forsave_patients()read_patients()toint1在成功写入或读取时提供,0否则。

请注意fclosein的验证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;
}

注意:patient_currentp您对in的重复分配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

再次检查,了解为什么要进行更改,并询问您是否还有其他问题。


推荐阅读