【问题标题】:Segmentation Fault in C using fgets(), strtok(), strcpy()使用 fgets()、strtok()、strcpy() 的 C 中的分段错误
【发布时间】:2018-02-07 17:02:08
【问题描述】:

我从下面显示的代码中得到一个段错误。我正在尝试创建一个简单的数据库,该数据库从 bin 文件中读取标准输入,并用逗号将其分割,将每个值放入数组中并将其放入结构中。我想对标准输入中的每一行都执行此操作,然后在最后将结构写入文件。

我从main 调用这个loadDatabase 函数。

数据在一行中如下所示,大约有 14 行:

34156155,MILES,NORMA,TAMMY,n/a,9/16/1964,FEMALE,123-45-6789,LAGUARDIA RD,SHLT,10915,n/a,n/a,CHESTER,NY,848-896-8296,n/a,NMILES@AMGGT.COM,n/a

这是我当前的代码。如果我的C真的很糟糕,请原谅。第一次……:

struct _record {
    char ID[25];
    char lname[25]; // last name
    char fname[25]; // first name
    char mname[25]; // middle name
    char suffix[25];    
    char bday[25];  
    char gender[25];    
    char SSN[25];   
    char add1[25]; //address 1
    char add2[25]; //address 2 
    char zip[25];
    char maiden[25];
    char MRN[25];
    char city[25];
    char state[25];
    char phone1[25];
    char phone2[25];
    char email[25];
    char alias[25];
};

bool loadDatabase(char *db_name) {
    printf("Loading Database...");
    char buffer[400];
    FILE *fp;
    int x;
    fp = fopen(db_name, "wb"); //write & binary option      
    if (fp == NULL) {
        puts(" ERROR: FILE CANNOT BE OPENED");
        return false; 
    } else {
        struct _record record; 
        while (fgets(rec, sizeof(rec), stdin) != NULL) {
            value = strtok(NULL, ",");
            flds[0] = strdup(value);

            //load lname
            value = strdup(NULL, ",");
            flds[1] = strdup(value);

            // load fname
            value = strdup(NULL, ",");
            flds[2] = strdup(value);

            // load mname
            value = strtok(NULL, "\n");
            flds[3] = strdup(value);
            // did not write the rest bc of the seg fault

            strcpy(record.ID, flds[0]);
            strcpy(record.lname, flds[1]);
            strcpy(record.fname, flds[2]);
            strcpy(record.mname, flds[3]);
            strcpy(record.suffix, flds[4]);
            strcpy(record.bday, flds[5]);  
            strcpy(record.gender, flds[6]);  
            strcpy(record.SSN, flds[7]);  
            strcpy(record.add1, flds[8]);  
            strcpy(record.add2, flds[9]); 
            strcpy(record.zip, flds[10]);
            strcpy(record.maiden, flds[11]);
            strcpy(record.MRN, flds[12]);
            strcpy(record.city, flds[13]);
            strcpy(record.state,  flds[14]);
            strcpy(record.phone1, flds[15]);
            strcpy(record.phone2, flds[16]);
            strcpy(record.email, flds[17]);
            strcpy(record.alias, flds[18]);
        }
        printf("ID: %s", record.ID);
        fwrite(record, sizeof(struct _record), 1, fp);
        fclose(fp);
    }
    return true;
}

【问题讨论】:

  • 是时候诚实地学习 gdb 了。
  • @MillieSmith 您的评论非常适用。您想记下以下链接吗?它们使像您这样的 cmets 更有帮助。 stackoverflow.com/questions/2069367/how-to-debug-using-gdbericlippert.com/2014/03/05/how-to-debug-small-programs
  • @Yunnosch 当我有像你这样的好队友时,为什么我自己要这样做;)。下次吧。感谢您的链接。 (老实说,虽然我们的朋友 Neal 应该可以 google gdb)。
  • value = strtok(NULL, ","); 也许你想第一次打电话给rec 而不是NULL。也许看看strtok的文档
  • fwrite(record, sizeof(struct _record),1,fp); 也许你想使用&record 而不是record

标签: c database pointers strtok strcpy


【解决方案1】:

您的代码中存在多个问题:

  • 没有提供fld 的定义。应该定义为19个char *的本地数组:

    char *fld[19];
    
  • 您有一些剪切+粘贴错误:value = strdup(NULL, ","); 而不是 value = strtok(NULL, ",");

  • 很多行丢失。

  • 你永远不会测试strtok() 是否返回NULL。无效的输入会导致未定义的行为

  • 不需要存储字符串:您可以直接将字符串复制到记录字段中。

  • 在使用strcpy 复制字符串之前,您不会检查字符串的长度。无效输入可能导致缓冲区溢出。

  • fwrite 的参数应该是记录的地址,而不是它的值:

    fwrite(&record, sizeof(struct _record), 1, fp);
    
  • 使用strtok()(或sscanf()%[^,] 转换说明符)不能正确处理空字段:strtok() 会将, 的任何序列视为单个分隔符(%[^,] 不会匹配一个空字段)。我建议为此使用一个函数。

  • 应该在每一行之前清除record 结构以避免将未初始化的内容存储到数据库文件中。

为避免其中一些问题,您应该提高警告级别并让编译器生成常见编程错误的诊断:gcc -Wall -Wextra -Werrorclang -Weverythingcl /W4

这是一个改进的版本:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

struct _record {
    char ID[25];
    char lname[25]; // last name
    char fname[25]; // first name
    char mname[25]; // middle name
    char suffix[25];
    char bday[25];
    char gender[25];
    char SSN[25];
    char add1[25]; //address 1
    char add2[25]; //address 2
    char zip[25];
    char maiden[25];
    char MRN[25];
    char city[25];
    char state[25];
    char phone1[25];
    char phone2[25];
    char email[25];
    char alias[25];
};

bool loadField(char *dest, int size, char **cursorp) {
    bool truncated = false;
    int i = 0;
    char *p;
    for (p = *cursorp; *p != '\0' && *p != '\n'; p++) {
        if (*p == ',') {
            p++;  // skip the comma separator
            break;
        }
        if (i + 1 < size) {
            dest[i] = *p;
        } else {
            truncated = 1;
        }
        i++;
    }
    // pad the field with null bytes
    while (i < size) {
        dest[i++] = '\0';
    }
    *cursorp = p;
    if (truncated) {
        fprintf(stderr, "field too long: %.*s\n", i, *cursorp);
        return false;
    } else {
        return true;
    }
}

bool loadDatabase(const char *db_name) {
    char buffer[1000];
    FILE *fp;

    printf("Loading Database...");
    fp = fopen(db_name, "wb"); //write & binary option
    if (fp == NULL) {
        fprintf(stderr, "error: cannot open file %s: %s\n", db_name, strerror(errno));
        return false;
    } else {
        struct _record record;    // clear the record
        while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
            char *cursor = buffer;

            memset(&record, 0, sizeof(record));    // clear the record
            loadField(record.ID, sizeof(record.ID), &cursor);
            loadField(record.lname, sizeof(record.lname), &cursor);
            loadField(record.fname, sizeof(record.fname), &cursor);
            loadField(record.mname, sizeof(record.mname), &cursor);
            loadField(record.suffix, sizeof(record.suffix), &cursor);
            loadField(record.bday, sizeof(record.bday), &cursor);
            loadField(record.gender, sizeof(record.gender), &cursor);
            loadField(record.SSN, sizeof(record.SSN), &cursor);
            loadField(record.add1, sizeof(record.add1), &cursor);
            loadField(record.add2, sizeof(record.add2), &cursor);
            loadField(record.zip, sizeof(record.zip), &cursor);
            loadField(record.maiden, sizeof(record.maiden), &cursor);
            loadField(record.MRN, sizeof(record.MRN), &cursor);
            loadField(record.city, sizeof(record.city), &cursor);
            loadField(record.state, sizeof(record.state), &cursor);
            loadField(record.phone1, sizeof(record.phone1), &cursor);
            loadField(record.phone2, sizeof(record.phone2), &cursor);
            loadField(record.email, sizeof(record.email), &cursor);
            loadField(record.alias, sizeof(record.alias), &cursor);
            printf("ID: %s\n", record.ID);
            if (fwrite(&record, sizeof(record), 1, fp) != 1) {
                fprintf(stderr, "error: cannot write record: %s\n", strerror(errno));
                break;
            }
        }
        fclose(fp);
    }
    return true;
}

int main(void) {
    if (loadDatabase("database.bin"))
        return 1;
    return 0;
}

【讨论】:

    猜你喜欢
    • 2016-07-21
    • 2019-02-16
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2014-12-21
    • 1970-01-01
    • 2016-02-24
    • 1970-01-01
    相关资源
    最近更新 更多