【问题标题】:Reading and Writing Structures [C]读写结构 [C]
【发布时间】:2010-03-04 20:21:05
【问题描述】:

重要编辑:

对不起大家,我在结构上犯了一个大错误。 字符*名称;意味着在结构之外,在结构之后写入文件。 这样,您读取结构,找出名称的大小,然后读取字符串。还解释了为什么不需要空终止符。 但是,我觉得在某个地方,我的实际问题已经得到解答。如果有人想编辑他们的回复,我可以选择最合适的回复。

再次,我要问的问题是“如果您阅读一个结构,您是否也在阅读它所包含的数据,或者您是否需要以其他方式访问它”。

抱歉给您带来了困扰

对于一项任务,我的任务是编写一个程序,该程序将结构写入和读取磁盘(使用 fread 和 fwrite)。

我无法理解这个概念。 假设我们有这样的结构:

typedef struct {
    short nameLength;
    char* name;
}attendenceList;

attendenceList names;

现在假设我们给它这个数据:

names.name = "John Doe\0";
names.nameLength = strlen(names.name); /*potentially -1?*/

然后我们使用 fwrite... 给定一个文件指针 fp。

fwrite(&names,sizeof(names),1,fp);

现在我们关闭文件,稍后打开它以读取结构。 问题是:当我们读取结构时,我们是否也在读取它存储的变量?

那么我们现在可以做类似的事情吗:

if(names.nameLength < 10)
{
 ...
}

或者我们是否必须比结构更复杂,或者以某种方式分配它们? 假设 fread 是:

fread(&names,sizeof(names),1,fp);

还假设我们已经在当前函数中定义了结构,如上所述。

感谢您的帮助!

【问题讨论】:

  • 几个cmets:"John Doe"e之后已经有一个0,所以你不需要写"John Doe\0"。其次,strlen 返回size_t,这是一个无符号类型。因此,它不能返回负值。当您将strlen 的返回值分配给short 时,如果该值在short 的范围内,则一切正常。否则,你会溢出。
  • 编辑的答案是,如果你用fwrite 写一个struct,然后用fread 读回它,你会得到它,但如果struct 有指针在其中,您可以返回指针的值,这在阅读时可能没有意义。如果你的struct 里面只有一个short 元素,我不知道你为什么不能写short 本身而不是把它包装在struct 中。也许您的实际struct 中有更多数据?只要struct 中不涉及指针,就可以。当然,写入二进制数据是不能跨不同机器移植的。
  • 实际的struct包含两个字符串的大小(跟在structs后面,所以你可以正确使用fseek,或者知道怎么读),这个程序连续使用了两次。

标签: c data-structures fread structure


【解决方案1】:

你这里有问题:

fwrite(&names,sizeof(names),1,fp);

由于attenceList 将名称保存为char *,这只会写出指针,而不是实际文本。当你读回它时,指针所引用的内存很可能还有其他东西。

你有两个选择:

  1. 将字符数组 (char names[MAXSIZE]) 放入出席列表。
  2. 不要编写原始数据结构,而是编写必要的字段。

【讨论】:

    【解决方案2】:

    您正在编写结构的内存布局,其中包括其成员。

    如果你再次读回结构,你会得到它们 - 至少如果你在同一平台上使用相同的编译器和编译器设置编译的程序。

    您的name 成员被声明为一个字符,因此您不能在其中存储字符串。

    如果name 是这样的指针:

    typedef struct {
        short nameLength;
        char *name;
    }attendenceList;
    

    您真的不应该将结构读/写到文件中。您将按照其在内存中的布局编写结构,其中包括 name 指针的值。

    fwrite 对结构中的指针一无所知,它不会跟随指针,也不会写入指针指向的任何内容。

    当你再次读回结构时,你会读入name指针中的地址,这可能不再指向任何有意义的东西。

    如果您将name 声明为一个数组,就可以了,因为数组及其内容是结构的一部分。

    typedef struct {
        short nameLength;
        char name[32];
    }attendenceList;
    

    与往常一样,请确保您不要尝试将大于 32 的字符串(包括其 nul 终止符)复制到 name。当您再次读取它时。设置 yourstruct.name[31] = 0;所以你确定缓冲区是空终止的。

    要写一个结构,你会这样做

    attendenceList my_list;
    
    //initialize my_list
    if(fwrite(&my_list,sizeof my_list,1,f) != 1) {
     //handle error
    }
    

    然后再读一遍:

    attendenceList my_list;
    
    //initialize my_list
    if(fread(&my_list,sizeof my_list,1,f) != 1) {
     //handle error
    }
    

    }

    【讨论】:

      【解决方案3】:

      我假设您的意思是 char* name 而不是 char namesizeof(name) 也将返回 4,因为您得到的是 char* 的大小,而不是 char 数组的长度。所以你应该在你的fwrite中写strlen(name)而不是sizeof(name)

      在您上面的示例中,我建议存储字符串的确切大小而不使用空终止。您不需要存储字符串长度,因为您可以在之后得到它。

      如果您只是从文件中读取一个字符串,并且您写入了没有空终止符的确切大小。然后,您需要在读取数据后手动终止缓冲区。 因此,请确保至少分配您正在读取的数据大小加上 1。
      然后你可以将该数组的最后一个字节设置为'\0'

      如果你一次将整个结构写入缓冲区,你应该小心,因为有填充。填充可能并不总是相同。

      当我们读入结构体时,我们是否也在读入它存储的变量?

      是的,但是您遇到的问题是,正如我上面提到的,您将存储指针 char*(4 个字节)而不是实际的 char 数组。我建议单独存储结构元素。

      【讨论】:

      • 嗯?我不明白你的意思是什么'[我的]缓冲区'?啊,谢谢,将修复我的代码。这只是一个例子,因为我不想在实际作业附近使用任何东西。要获取字符串的长度(它实际上来自文件,所以使用 fread)我需要 -1 strlen 吗?
      • strlen 返回不包括空终止符的字符串长度。
      • 感谢您的澄清,但我的实际问题仍未得到解答。
      • @Blackbinary:更新了我的答案以回答您的确切问题。
      【解决方案4】:

      你问:

      现在我们关闭文件,稍后打开它以读取结构。问题是:当我们读入结构体时,我们是否也在读入它存储的变量?

      没有。 sizeof(names) 是在编译时定义的常量值。这将与

      相同
      sizeof(short) + sizeof(void*) + some_amount_of_padding_to_align_things
      

      它将包含names.name指向的大小,它只会包含指针本身的大小。

      因此,将其写入文件时会遇到两个问题。

      1. 您实际上并未将名称字符串写入文件
      2. 您正在向文件写入一个指针值,当您读回它时将没有任何意义。

      由于您当前编写的代码,当您回读名称时,names.name 将指向某个地方,但不会指向“John Doe\0”。

      你需要做的是写names.name指向的字符串而不是指针值。

      您需要做的有时称为“展平”结构,您在内存中创建一个不包含指针的结构,但保存与您要使用的结构相同的数据,然后将展平结构写入磁盘。这是一种方法。

      typedef struct {
          short nameLength;
          char  name[1]; // this will be variable sized at runtime.
      }attendenceListFlat;
      
      int cbFlat = sizeof(attendenceListFlat) + strlen(names.name);
      attendenceListFlat * pflat = malloc(cbFlat);
      pflat->nameLength = names.nameLength;
      strcpy(pflat->name, names.name);
      
      fwrite(pflat, cbFlat, 1, fp);
      

      扁平结构以最小大小为 1 的数组结束,但是当我们 malloc 时,我们添加了 strlen(names.name),因此我们可以将其视为 strlen(names.name)+1 大小的数组。

      【讨论】:

        【解决方案5】:

        一些事情。

        结构只是内存块。它只是占用一堆字节并在它们上绘制边界。访问结构元素只是将特定内存偏移转换为特定类型数据的一种便捷方式

        您正在尝试将字符串分配给 char 类型。这行不通。在 C 中,字符串是字符数组,其末尾有一个 NULL 字节。让它工作的最简单方法是为名称设置一个固定缓冲区。当您创建结构时,您必须将名称复制到缓冲区中(非常小心不要写入比缓冲区包含的更多字节)。然后,您可以一步从文件中写入/读取缓冲区。

        struct attendanceList {
             int  namelen;
             char name[256]; //fixed size buffer for name
        }
        

        另一种方法是让名称成为指向字符串的指针。这使您尝试做的事情变得更加复杂,因为为了将结构写入/读取文件,您必须考虑到名称存储在内存中的不同位置。这意味着两次写入和两次读取(取决于您的操作方式)以及正确地将 name 指针分配到您读取名称数据的任何位置。

        struct attendanceList {
            int   namelen;
            char* name; //the * means "this is a pointer to a char somewhere else in memory"
        }
        

        还有第三种方法可以做到这一点,使用动态大小的结构,在结构的末尾使用零长度数组的技巧。一旦知道名称的长度,就可以分配正确的数量(sizeof(struct admissionList) + 字符串长度)。然后你把它放在一个连续的缓冲区中。您只需要记住 sizeof(struct admissionList) 不是您需要写入/读取的大小。作为一个开始,这可能有点令人困惑。这也是一种并非所有编译器都支持的 hack。

        struct attendanceList {
           int namelen;
           char name[0];  //this just allows easy access to the data following the struct.  Be careful!
        

        }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-08-30
          • 2016-06-28
          • 2014-03-10
          • 1970-01-01
          • 1970-01-01
          • 2023-03-17
          • 1970-01-01
          • 2019-06-06
          相关资源
          最近更新 更多