【问题标题】:Write pointer to file in C用C写指向文件的指针
【发布时间】:2023-03-11 15:30:02
【问题描述】:

我有一个结构:

typedef struct student {
  char *name;
  char *surname;
  int age;
} Student;

我需要将结构写入二进制文件。
这是我的尝试:

Student *s = malloc(sizeof(*s));

我用数据填充我的结构,然后我将结构写入文件:

fwrite(s, sizeof(*s), 1, fp);

在我的文件中,姓名和姓氏不存在。相反,它们有各自的char * 指针地址。
如何将 char * 写入文件而不是指针的地址?

【问题讨论】:

    标签: c file pointers


    【解决方案1】:

    您需要取消引用您的指针并编写结构的各个部分。 (你不应该直接写一个结构,而是编码它的部分并写那些:

    Student *s = malloc(sizeof(*s));
    s->name = "Jon";
    s->surname = "Skeet";
    s->age = 34;
    
    // ....
    
    fwrite(s->name, sizeof(char), strlen(s->name) + 1, fp);
    fwrite(s->surname, sizeof(char), strlen(s->surname) + 1, fp);
    
    //This one is a bit dangerous, but you get the idea.
    fwrite(&(s->age), sizeof(s->age), 1, fp); 
    

    【讨论】:

    • 但我在结构中有大约 10 个字段。它的代码很长
    • 然后你写一个函数把它全部写出来;就这样吧;像这样:int fwriteStudent(Student *s, FILE *fp);,并且只记得在修改结构定义时保持该函数更新。
    • @Sergey:你可以将这些操作封装到一个函数中,所以它不必是“非常长的代码”。
    • 你的意思可能是 &s->age,否则更危险,而且极不可能产生想要的结果。
    【解决方案2】:

    你将不得不做一些额外的工作来写出结构元素指向的数据——可能只是逐个元素地写出来。

    或者将您的结构更改为以下内容:

    typedef struct
    {
        char name[MAX_NAME_LEN];
        char surname[MAX_SURNAME_LEN];
        int age;
    } Student;
    

    【讨论】:

    • @Williham:在某些情况下,固定字符串长度很好——对于小型数据集的作业问题,使用它们非常合适。使用打包但更复杂的表示是premature optimisation,用你的话说,“非常糟糕”。
    • 在我给出答案并发表评论后,[homework] 标签出现得很好。无论如何,[作业] 不能原谅草率的代码。
    • 人名之类的固定大小字段完全可以接受 - 毕竟,它们肯定有一个合理的上限(期望人名超过 500 个字符是否合理?)。只要代码能够检测并正确处理存储过长输入的尝试,就可以了。 (你知道在你最喜欢的文件系统上文件名的最大允许长度吗?你能猜出底层数据结构是什么样的吗?)
    • 浪费的空间是速度/空间的权衡——磁盘空间很便宜;比 CPU 周期便宜。不能存储某人的全名肯定是失败的,我绝对会提倡对该上限采取非常保守的方法 - 但认为没有合理的上限是愚蠢的。毕竟,输入 500 个字符的名称而没有任何错误的机会几乎为零。此外,用户级应用程序中糟糕的性能可能与文件系统中的问题一样严重(人们在 Oracle 中使用 VARCHAR2 而不是到处使用 CLOB 是有原因的)!
    • 也许,但你也可以说“不要使用fwrite,使用SQLite”。如果需要使用fwrite,那么固定大小的文本字段可能是合适的。您的一揽子声明“固定字符串长度不好”过于教条,在许多情况下都是错误的。这是一个权衡,这意味着有时它会是正确的答案,有时不是。
    【解决方案3】:

    如果您想直接将结构原样写入文件,您需要定义姓名和姓氏的大小,而不是动态分配它。

    typedef structure student { char name[100]; char surname[100]; int age; } 
    

    否则,您需要将每个信息一一写入结构中。

    【讨论】:

    • @Williham Totland - 我已经给出了你会这样做的理由。即,如果您想按原样编写结构。不要只是盲目地评论这很糟糕。如果您想在一次 fwrite 操作中编写结构,您有什么更好的主意吗?
    • 将结构体写成一个 fwrite 操作,同样如前所述,是一个 Bad Idea™。它几乎但不完全是完全不可移植的,并且并不总是产生预期的结果。可溢出字符串是等待发生的安全漏洞。
    • @Williham Totland - “它是不可移植的,并不总是想要的结果” - 在 C 中,这比一个一个地编写每个结构字段更便携。诸如安全漏洞之类的其他问题只是因为没有验证或您编写的代码完全错误而发生。
    • 在某些情况下,具有固定大小的结构当然是合适的——例如,如果我们将这些 student 记录中的 15,000 条写入文件,那么我们可以快速查找第 9,347 条,如果它们的大小都一样。如果每个都是可变大小的,那么我们必须首先读取所有 9,346 个前面的结构(或者有一个单独的索引结构)。
    • @caf:如果您有 15,000 条学生记录,您可能希望使用数据库,例如 SQLite。
    【解决方案4】:

    在 C 中,您必须手动序列化结构(将它们转换为一系列字节)。如果你想让你输出的数据被另一台机器读取,你必须使用字节顺序帐户endianness。

    这是一个序列化结构(将其写入文件)的简单示例,忽略字节顺序/不同的字长并使代码不可移植:

    size_t length;
    
    length = strlen(s->name) + 1;
    fwrite(&length, sizeof(length), 1, fp);
    fwrite(s->name, 1, length, fp);
    
    length = strlen(s->surname) + 1;
    fwrite(&length, sizeof(length), 1, fp);
    fwrite(s->surname, 1, length, fp);
    
    fwrite(&s->age, sizeof(s->age), 1, fp);
    

    反序列化:

    size_t length;
    
    fread(&length, sizeof(length), 1, fp);
    s->name = malloc(length);
    fread(s->name, 1, length, fp);
    
    fread(&length, sizeof(length), 1, fp);
    s->surname = malloc(length);
    fread(s->surname, 1, length, fp);
    
    fread(&s->age, sizeof(s->age), 1, fp);
    

    在实际应用中,您应该检查输入的有效性,而不是盲目地假设输入是有效且值得信赖的。此外,您需要决定使用什么字节顺序来存储 ints、size_ts 等,并确保通过使用位移以正确的字节顺序读取/写入它们。

    顺便说一句,你可能想看看tpl,一个简单的 C 二进制序列化库。

    【讨论】:

      【解决方案5】:

      您需要取消引用s 并单独写出结构的每个元素才能将内容实际写入文件:

      size_t nameCount = strlen(s->name) + 1;
      fwrite(s->name, sizeof(char), nameCount, fp);
      
      size_t surnameCount = strlen(s->surname) + 1;
      fwrite(s->surname, sizeof(char), surnameCount, fp);
      

      【讨论】:

      • 这不适用于发布的结构定义 - sizeof(s->name) 将给出 char * 的大小。
      • 所以现在您需要将其更改为 strlen() * sizeof(char)...(好吧,sizeof(char) 将是 1,只是为了原则。
      【解决方案6】:

      请注意,无论如何您将无法以“正常”形式查看字段,因为您将其写入二进制文件。尝试在 fopen 的 mode 参数中打开不带“b”的文件(r+\w\a 等而不是 rb+\wb\ab)。

      您可以看到,使用 fread 时(从二进制文件中,以“rb”模式打开它后,您应该得到预期的结构字段。

      【讨论】:

        【解决方案7】:

        正如其他人所说,您必须单独写出这些字段。可能有助于回读数据的一件事是在每个条目之前写出字符串长度,例如。

        size_t len;
        len = strlen(s->name)+1;
        fwrite(&len,sizeof(len),1,stream);
        fwrite(s->name,sizeof(*(s->name)),len,stream);
        ...
        

        回读是一个问题

        Student * s = malloc(sizeof(*s));
        size_t len;
        fread(&len,sizeof(len),1,stream);
        s->name = malloc(len);
        fread(s->name,sizeof(*(s->name)),len,stream);
        ...
        

        【讨论】:

          猜你喜欢
          • 2021-11-06
          • 2016-03-05
          • 1970-01-01
          • 2021-12-31
          • 1970-01-01
          • 1970-01-01
          • 2019-09-10
          • 2015-02-13
          • 1970-01-01
          相关资源
          最近更新 更多