【问题标题】:struct padding influence in C struct serialization ( saving to file )C结构序列化中的结构填充影响(保存到文件)
【发布时间】:2011-04-06 03:40:18
【问题描述】:

我在 C 中有以下结构:

typedef struct sUser {
    char name[nameSize];
    char nickname[nicknameSize];
    char mail[mailSize];
    char address[addressSize];
    char password[passwordSize];
    int totalPoints;
    PlacesHistory history;
    DynamicArray requests;
}User;

typedef struct sPlacesHistory {
    HistoryElement array[HistorySize];
    int occupied;
    int last;
}PlacesHistory;

和功能:

void serializeUser( User * user, FILE * fp ) {
    fwrite( user, nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ), 1, fp );
    serializeDynamicArray( user -> requests, fp );
}

User * loadUser( FILE * fp ) {
    User * user = malloc( sizeof( User ) );
    fread( user, nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ), 1, fp );
    user -> requests = loadDynamicArray( fp );

    return user;
}

当我加载 struct User 并打印该用户(从文件加载)时,placesHistory 的字段“last”的值为 255 或 -1,具体取决于 PlacesHistory 结构的字段顺序。但我保存的用户在该成员上的值为 -1。

所以当我得到 255 时,显然是错误的.. 我怀疑这与结构填充有关。

我怎样才能做到这一点,使得结构中字段的顺序无关紧要?
或者我需要遵循哪些标准才能使事情正常进行?
我需要一次 fwrite/fread 一个成员吗? (出于效率问题,我想避免这种情况)
我需要先序列化为数组而不是文件吗? (我希望不是 .. 因为这意味着由于 mallocated 数组而事先知道我所有结构的大小——这意味着需要额外的工作来为每个非简单结构创建一个函数来知道它的大小)

注意:*大小是定义的常量
注2:DynamicArray 是指向另一个结构的指针。

【问题讨论】:

  • 显而易见的事情就是读/写整个结构,fwrite(user,sizeof *user,1,fp) 你有什么理由不能这样做吗?如果您希望字段的顺序无关紧要,那就另当别论了,您需要使用某种结构化文件格式来实现这一点。
  • @nos:OP 可能没有这样做,因为 requests 不应该被读取。解决方案是有两个结构,一个带有要读取的数据,一个带有这个 and requests. 的主结构
  • 在这种情况下,使用 offsetof() 宏计算结构的大小,以排除最后一个元素,而不是使用 sizeof。
  • 不能做 fwrite( user, sizeof( User ), 1 fp ) 因为 requests 是指针类型...我需要使用 serializeDynamicArray 函数来序列化这些东西。

标签: c file serialization struct padding


【解决方案1】:

是的,它可能与 totalPointshistory 前面的填充有关。

您可以写出sizeof(User) - sizeof(DynamicArray) 并以相同的方式回读。当然,只要您的结构定义和编译器不改变,这将是兼容的。如果您不需要一个版本的程序的序列化数据与另一个版本兼容,那么上述方法应该可以工作。

【讨论】:

  • @joxnas,@nos 指出offsetof() 可能是解决同一概念的更好方法。在您的情况下使用fwrite(user, offsetof(User, requests), 1, fp)。这两个建议都有效,因为您的 DynamicArray 成员是最后一个。 offsetof 方法更灵活,可以在您想要省略不是最终成员的成员时使用。
  • 好的,谢谢你的提示。再解释一件事:如果 requests 不是最后一个成员,假设我用邮件更改了订单切换请求。我现在如何使用 offsetof()?
  • @joxnas, fwrite(user, offsetof(User, requests), 1, fp); serializeDynamicArray(user->requests, fp); fwrite(&user.address, sizeof(User) - offsetof(User, address), 1, fp);
  • 谢谢大家!当我获得所需的声誉时,我会投票给有用或/和信息丰富的答案。
【解决方案2】:

为什么要单独添加所有元素?这只是增加了很多错误的空间。每当您更改结构时,如果您忘记更改所有增加大小的地方,您的代码可能会中断(实际上,为什么每次都添加它?)。

而且,正如您所怀疑的,您的代码也没有考虑结构填充,因此您可能会在数据块的末尾丢失最多三个字节(如果您的最大元素是 4 个字节)。

为什么不sizeof(User) 来获取您正在读取/写入的数据量的大小?如果您不想保存其中的一部分(如请求),请在结构中使用结构。 (编辑:或者,像 rlibby 建议的那样,只需减去您不想阅读的部分的 sizeof。)

我的猜测是您的字符串大小不能被 4 整除,因此您的长度为 3 个字节,因此,您可能本应读取“0xffffffff”(=-1),但最终只读取“0xff000000 "(=255 使用小端,并假设您的结构最初被清零)。

【讨论】:

  • 感谢您的帮助。我想我会尝试 rlibby 的建议;)
【解决方案3】:

填充可能是您的问题,因为

nameSize + nicknameSize + mailSize + addressSize + passwordSize + sizeof( int ) + sizeof( PlacesHistory ) != sizeof( User ) 

所以最后一个成员(和结构中的最后一个)保持未初始化。要检查这一点,请在从文件读取之前执行 memset(,0,sizeof(User))。

要解决此问题,请使用 #pragma pack(push,1) before 和 #pragma pack(pop) after

【讨论】:

  • #pragma pack 是一个非常糟糕的主意。未对齐的整数可能会导致严重的性能影响,甚至彻底崩溃(例如在 PowerPC 上)。
  • 性能影响 - 是的,但为什么会崩溃?它是从文件中读取/写入结构的唯一选择。否则,如果您更改编译器,您将遇到麻烦...可能未与 1 字节对齐,但对齐是必须的,而不是选项
  • PowerPC 从 int-aligned 地址读取 int。如果您尝试从 0x80000001 之类的地址(即不能被 4 整除)读取 4 字节 int,它将崩溃。
猜你喜欢
  • 2014-10-12
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
  • 1970-01-01
  • 2015-04-25
相关资源
最近更新 更多