简体   繁体   English

使用fgets(),strtok(),strcpy()在C中进行分段错误

[英]Segmentation Fault in C using fgets(), strtok(), strcpy()

I am getting a seg fault from the code shown below. 我从下面显示的代码中遇到段错误。 I am trying to create a simple database that reads a stdin from a bin file chops it up by commas, throws each value into an array and throws it in a struct. 我正在尝试创建一个简单的数据库,该数据库从bin文件中读取标准输入,并用逗号将其砍掉,然后将每个值放入数组中,然后将其放入结构中。 I want to do this for every line in the standard input and then write the struct to a file at the end. 我想对标准输入中的每一行执行此操作,然后将结构最后写入文件。

I am calling this loadDatabase function from main . 我从main调用此loadDatabase函数。

The data looks like the following in one line and is about 14 lines long: 数据看起来像下面一行一样,大约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

Here is my current code. 这是我当前的代码。 Please excuse me if my C is really bad. 如果我的C真的不好,请原谅。 First time...: 第一次...:

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;
}

There are multiple problems in your code: 您的代码中存在多个问题:

  • the definition of fld is not provided. 没有提供fld的定义。 It should be defined as a local array of 19 char * : 应该将其定义为19个char *的本地数组:

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

  • many lines are missing. 许多行都丢失了。

  • you never test if strtok() returns NULL . 您永远不会测试strtok()返回NULL Invalid input will cause undefined behavior 无效的输入将导致未定义的行为

  • memory for the strings is unnecessary: you can just copy the string directly into the record field. 不需要存储字符串:您可以直接将字符串复制到记录字段中。

  • you do not check the length of the strings before copying them with strcpy . 使用strcpy复制字符串之前,您无需检查字符串的长度。 Invalid input may cause buffer overflows. 输入无效可能导致缓冲区溢出。

  • the argument to fwrite should be the address of the record, not its value: fwrite的参数应该是记录的地址,而不是它的值:

     fwrite(&record, sizeof(struct _record), 1, fp); 
  • using strtok() (or sscanf() with %[^,] conversion specifiers) does not handle empty fields correctly: strtok() will consider any sequence of , as a single separator ( %[^,] will not match an empty field either). 使用strtok() (或带有%[^,]转换说明符的sscanf() )不能正确处理空字段: strtok()将考虑的任何序列,因为单个分隔符( %[^,]将不匹配空字段要么)。 I suggest using a function for this. 我建议为此使用一个函数。

  • the record structure should be cleared before each line to avoid storing uninitialized contents into the database file. record结构应在每行之前清除,以避免将未初始化的内容存储到数据库文件中。

To avoid some of these problems, you should raise the warning level and let the compiler produce diagnostics for common programming errors: gcc -Wall -Wextra -Werror or clang -Weverything or cl /W4 . 为了避免这些问题,您应该提高警告级别,并让编译器生成常见编程错误的诊断信息: gcc -Wall -Wextra -Werrorclang -Weverythingcl /W4

Here is an improved version: 这是一个改进的版本:

#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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM