【问题标题】:c BAD access fscanf with structsc BAD 使用结构访问 fscanf
【发布时间】:2019-12-08 17:50:23
【问题描述】:

我正在读取格式如下的文本文件:

名字 姓 年龄 NumberOfSiblings 母亲父亲

导入头文件:

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

结构体定义如下:

typedef struct {
    int person_ID; //not included in file
    char full_name[20];
    char sex[2];
    char countryOfOrigin[20];
    int num_siblings;
    float parentsAges[2]; //this should store mother and fathers age in an array of type float
} PersonalInfo;


void viewAllPersonalInformation(){
    FILE* file = fopen("People.txt", "r");
    if (file == NULL){
        printf("File does not exist");
        return;
    }
    int fileIsRead = 0;
    int idCounter = 0;

    PersonalInfo People[1000];
    //headers
    printf("%2s |%20s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");

    do{
        fileIsRead = fscanf(file, "%s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);

        People[idCounter].person_ID = idCounter;
        printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
        idCounter++;
    }
    while(fileIsRead != EOF);
    fclose(file);


    printf("Finished reading file");
}


int main() {
    viewAllPersonalInformation();
    return 0;
}

People.txt 的样子:

约翰·奥唐纳 F 爱尔兰 3 32.5 36.1

玛丽·麦克马洪 M 英格兰 0 70 75

彼得汤普森 F 美国 2 51 60

【问题讨论】:

  • 你能分享一个最小的例子吗?
  • 你传递给fscanf的指针是从未初始化的内存中读取的。
  • @gsamaras 抱歉,我没有完全理解一个最小的例子?我需要遍历文件,将数据分配给结构并打印。我尝试使用 fscanf 来获取值并使用 printf 来打印它们。不确定要省略什么?
  • @user2363025 一个完整的最小示例,包含您的主结构、结构、在读取数据之前为指针分配内存的方式以及读取数据的方式。根据您现在的问题,可以得出您忘记为指针分配内存的结论。是这样吗?一个完整的最小示例将毫无疑问,并且不需要假设;)
  • @gsamaras 你确实跳到了正确的方向:) 我需要动态创建内存,但作为一个起点,我已经修改了结构,以便全名和国家/地区是固定长度 20 和浮动数组是固定长度 2。但程序仍然在 fileread 行崩溃

标签: c arrays string struct typedef


【解决方案1】:

fscanf() 将在遇到空格时停止读取。在全名的情况下,您希望使用格式说明符%s 读取两个字符串。 %s 一发现空格就停止,所以它只会将名字存储在 full_name 中,而姓氏将转到第二个 %s,因此在 countryOfOrigin 中。

所以,如果你想阅读“Peter Thompson”,那么你需要引入两个字符串(字符数组)来存储名字和姓氏,然后将它们连接起来。

但是,由于您要读取单词数不同的全名,我建议您使用fgets()(它也具有缓冲区溢出保护)。例如,“Peter Thompson”有 2 个,“Mary Mc Mahon”有 3 个。所以,如果你坚持使用 fscanf(),你会使用多少个 %s? 2个还是3个?您不知道,这取决于您在运行时获得的输入。也许有一些正则表达式可以使用fscanf() 来解决问题,但相信使用fgets() 然后解析文件读取的行更适合练习。


现在我们读取了带有fgets() 的文件行,我们如何处理它?我们仍然不知道每个全名包含的单词数!如何发现?通过计算该行包含的空格。如果它包含w 空格,那么它有w + 1 标记(在您的示例中可以是单词、数字或字符)。

通过一个简单的 if-else 语句,我们可以在您的示例中区分这两种情况,当有 6 个空格(7 个标记)和 7 个空格(“Mary Mc Mahon M England 0 70 75”的 8 个标记)时。

现在,如何从字符串(行)中提取到标记(全名、年龄等)?我们可以有一个循环并使用一堆 if-else 语句来表示,直到我找到第二个(或第三个,取决于空格的数量)空格,我将把当前标记附加到 full_name。然后,下一个标记将是性别,以此类推。

当然你可以这样做,但由于我有点懒,我将基于你与fscanf() 的出色工作,并改用sscanf() 来提取令牌。当然,使用这种方法,我们需要使用一两个(取决于空格的数量)额外的字符串,以便临时存储姓氏(在我们将其附加到带有strcat() 的名称之前)。

最小的完整工作示例:

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

#define P 1000 // Max number of people
#define L 256  // Max length of line read from file (-1)

typedef struct {
    int person_ID; //not included in file
    char full_name[32];
    char sex[2];
    char countryOfOrigin[16];
    int num_siblings;
    float parentsAges[2];
} PersonalInfo;

int count_whitespaces(char* str)
{
    int whitespaces_count = 0;
    while(*str)
    {
        if(*str == ' ')
            whitespaces_count++;
        str++;
    }
    return whitespaces_count;
}

void viewAllPersonalInformation(){
    FILE* file = fopen("People.txt", "r");
    if (file == NULL){
        printf("File does not exist");
        return;
    }
    int fileIsRead = 0;
    int idCounter = 0;

    PersonalInfo People[P];
    // line of file, placeholder for biworded surnames, surname.
    char line[L], str[8], surname[16];
    //headers
    // You have 7 format specifiers for the headers, but only 6 six in fscanf!!!
    printf("%2s |%5s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");

    // read into 'line', from 'file', up to 255 characters (+1 for the NULL terminator)
    while(fgets(line, L, file) != NULL) {
        //fileIsRead = fscanf(file, "%s %s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
        // eat trailing newline of fgets
        line[strcspn(line, "\n")] = 0;

        // Skip empty lines of file
        if(strlen(line) == 0)
            continue;

        if(count_whitespaces(line) == 6)
        {
            sscanf(line, "%32s %16s %c %16s %d %f %f", People[idCounter].full_name, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
        }
        else // 7 whitespaces, thus 8 token in the string
        {
            sscanf(line, "%32s %8s %16s %c %16s %d %f %f", People[idCounter].full_name, str, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
            // Separate name and first word of surname with a space
            strcat(People[idCounter].full_name, " ");
            strcat(People[idCounter].full_name, str);
        }

        // Separate name and surname with a space
        strcat(People[idCounter].full_name, " ");
        strcat(People[idCounter].full_name, surname);

        People[idCounter].person_ID = idCounter;
        printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
        idCounter++;
        if(idCounter == P)
        {
            printf("Max number of people read, stop reading any more data.\n");
            break;
        }
    };
    fclose(file);

    printf("Finished reading file.\n");
}


int main() {
    viewAllPersonalInformation();
    return 0;
}

输出:

ID | Name |Sex |   Born In |Number of siblings |Mother's age |Father's Age
0 John O'Donnell F Ireland 3 32.500000 36.099998
1 Mary Mc Mahon M England 0 70.000000 75.000000
2 Peter Thompson F America 2 51.000000 60.000000
Finished reading file.

您注意到sscanf() 格式说明符中的数字了吗?他们是guarding from buffer overflows


动态内存分配怎么样?

在上面的代码中,我估计了姓名、原籍国等的最大长度。现在如何让这些尺寸动态化?我们可以,但我们仍然需要初步估计。

因此,我们可以在一个固定长度的临时数组中读取名称,然后用strlen() 找到字符串的实际长度。有了这些信息,我们现在可以动态分配内存(通过 char 指针指向),然后使用strcpy() 将字符串从临时数组复制到其最终目的地。

【讨论】:

    【解决方案2】:

    如果你有一个指针字段char *full_name,这意味着只是一个指针,应该由一些存在的对象初始化,如果是char *,它通常应该是一个字符数组。您可以通过两种方式修复它:

    • 只要做一个像char full_name[100]这样的数组字段,然后将一个字符串的最大长度传递给一个scanf格式的字符串,像%100s这样,这是最简单的方法;
    • 使用malloc 函数,不要忘记free 该地址,或以其他方式为指针分配一些有效地址,例如将数组声明为通常的自动存储变量,并将零索引元素的地址分配给指针字段,并记住离开函数后自动存储变量的地址将变为无效。

    还有一个麻烦。 %s 转换说明符告诉 fscanf 读取 单个 单词,直到 any 空白字符(如空格),因此根据您的输入格式,您的 full_name 字段将是读取到第一个空格,任何进一步读取整数的尝试都将失败。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-05
      • 1970-01-01
      • 2012-10-17
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多