读取文件并填充结构体

4

我有一个以下定义的结构体:

typedef struct myStruct{
    int a;
    char* c;
    int f;
} OBJECT;

我能够填充这个对象并将其写入文件,但是我无法读取其中的char* c值......在尝试读取时,它会给我一个分段错误。我的代码有什么问题吗?
//writensave.c

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


#define p(x) printf(x)

int main()
{
    p("Creating file to write...\n");
    FILE* file = fopen("struct.dat", "w");
    if(file == NULL)
    {
        printf("Error opening file\n");
        return -1;
    }

    p("creating structure\n");
    OBJECT* myObj = (OBJECT*)malloc(sizeof(OBJECT));
    myObj->a = 20;
    myObj->f = 45;
    myObj->c = (char*)calloc(30, sizeof(char));
    strcpy(myObj->c, 
        "This is a test");
    p("Writing object to file...\n");
    fwrite(myObj, sizeof(OBJECT), 1, file);
    p("Close file\n");
    fclose(file);
    p("End of program\n");
    return 0;       
}

这是我正在尝试阅读它的方式:
//readnprint.c
#include "mystruct.h"
#include <stdio.h>
#define p(x) printf(x)
int main()
{   
    FILE* file = fopen("struct.dat", "r");
    char* buffer;
    buffer = (char*) malloc(sizeof(OBJECT));
    if(file == NULL)
    {
        p("Error opening file");
        return -1;
    }

    fread((void *)buffer, sizeof(OBJECT), 1, file);
    OBJECT* obj = (OBJECT*)buffer;
    printf("obj->a = %d\nobj->f = %d \nobj->c = %s",
        obj->a,
        obj->f,
        obj->c);
    fclose(file);
    return 0;
}

需要编写数据指针所指的内容,而不是它所包含的地址。 - Learner
5个回答

2

您的第一个代码示例似乎假定字符串不会超过30个字符。如果是这样,最简单的修复方法可能是重新定义结构,如下所示:

typedef struct myStruct{
    int a;
    char c[30];
    int f;
} OBJECT;

否则,你只是在存储一个指向动态分配内存的指针,当你的程序退出时该内存将被销毁(因此,当你稍后检索该指针时,地址将无效,并且很可能无法访问)。

结构体对齐有什么问题吗? - Learner
结构体对齐怎么处理?只要读取数据的应用程序与写入数据的应用程序相同,这就不是问题。任何填充都将在写入时成为未初始化数据,并且在读取时可能会更改,但您首先并不指望它处于任何特定状态... - dash-tom-bang
如果您关心结构对齐,那么您的编译器很可能有一个选项来“压缩”您的结构(即不使用填充字节)。在gcc中,您可以在struct定义的闭合大括号后添加__attribute__((__packed__))。然而,正如dash-tom-bang指出的那样,如果您从同一应用程序读取和写入数据,则对齐不应该是一个问题。 - bta

2
当您编写对象时,您将指针值写入文件而不是指向的信息。
您需要做的是,不仅仅将整个结构体fwrite/fread,而是逐个字段进行。像处理对象一样fwrite a和f,但是对于字符串,您需要特殊处理。尝试fwrite/fread长度(在数据结构中未表示,这很好),然后再fwrite/fread字符缓冲区。当读取时,您当然需要分配它。

1

你的结构体中的char *字段被称为可变长度字段。当你写入这个字段时,需要一种方法来确定文本的长度。两种流行的方法是:
1. 先写入大小
2. 写入终止字符

先写入大小
在这种方法中,文本数据的大小首先被写入,紧接着是数据本身。
优点:通过块读取可以更快地加载文本。
缺点:需要两次读取,需要额外的空间来存储长度数据。
示例代码片段:

struct My_Struct
{
   char * text_field;
};

void Write_Text_Field(struct My_Struct * p_struct, FILE * output)
{
  size_t text_length = strlen(p_struct->text_field);
  fprintf(output, "%d\n", text_length);
  fprintf(output, "%s", p_struct->text_field);
  return;
}

void Read_Text_Field(struct My_STruct * p_struct, FILE * input)
{
  size_t text_length = 0;
  char * p_text = NULL;
  fscanf(input, "%d", &text_length);
  p_text = (char *) malloc(text_length + sizeof('\0'));
  if (p_text)
  {
     fread(p_text, 1, text_length, input);
     p_text[text_length] = '\0';
  }
}

写终止字符 在这种方法中,文本数据后面跟着一个“终止”字符进行写入。与 C 语言字符串非常相似。 优点:需要比 Size First 方法占用更少的空间。
缺点:必须逐字节读取文本,以便不会错过终止字符。

固定大小字段
不使用 char* 作为成员,而是使用 char [N],其中 N 是字段的最大大小。 优点:可以将固定大小的记录作为块读取。 使文件中的随机访问更容易。 缺点:如果未使用所有字段空间,则会浪费空间。 当字段大小太小时会出现问题。

在将数据结构写入文件时,应考虑使用数据库。有小型数据库(如 SQLite)和大型数据库(如 MySQL)。当已经编写并测试了数据的永久存储例程时,不要浪费时间编写和调试它们。


@deostroll:我已经添加了第一种方法的代码。第二种方法可以使用标准库函数进行字符串输入/输出。 - Thomas Matthews
就像您之前提到的那样,我已经保留了一个字段来存储字符串的长度(到obj->a)...我已经将所有这些内容写入文件。但是,当我尝试读取时仍然会出现段错误。> http://pastebin.com/UuDGVQxZ - deostroll

1
你正在保存指向 char 的指针,而不是字符串本身。当你尝试重新加载文件时,你在一个具有不同地址空间的新进程中运行,该指针不再有效。相反,你需要按值保存字符串。

1

我想提及一个潜在的可移植性问题,这取决于数据文件的计划使用情况,可能存在也可能不存在。

如果数据文件将在不同字节序的计算机之间共享,则需要为非字符类型(int、short、long、long long等)配置文件到主机和主机到文件转换器。此外,最好使用来自 stdint.h 的类型(int16_t、int32_t等)以保证所需大小。

但是,如果数据文件不会在任何地方移动,则可以忽略这两点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接