为多个平台使用存储 C 结构——这种方法行得通吗?

Store C structs for multiple platform use - would this approach work?

编译器:GNU GCC

应用类型:控制台应用

语言:C

平台:Win7 和 LinuxMint

我写了一个程序,想在Win7和Linux下运行。该程序将 C 结构写入文件,我希望能够在 Win7 下创建文件并在 Linux 中读回,反之亦然。

到目前为止,我了解到使用 fwrite() 编写完整的结构将几乎 100% 地保证它不会被其他平台正确读回。这是由于填充和其他原因造成的。

我自己定义了所有结构,它们(现在,在我之前在这个论坛上的问题之后)都有类型为 int32_t、int64_t 和 char 的成员。我正在考虑为每个结构编写一个 WriteStructname() 函数,它将各个成员作为 int32_t、int64_t 和 char 写入输出文件。同样,ReadStructname() 函数用于从文件中读取各个结构成员并将它们再次复制到空结构。

这种方法行得通吗?我更喜欢最大限度地控制我的源代码,所以除非我真的必须这样做,否则我不会寻找库或其他依赖项来实现这一点。

感谢阅读

Element-wise 将数据写入文件是最好的方法,因为 structs 将 differ due to alignment and packing differences between compilers.

然而,即使采用您计划使用的方法,仍然存在潜在的缺陷,例如 endianness between systems, or different encoding schemes (ie: two's complement versus one's complement encoding of signed numbers)。

如果你打算这样做,你应该考虑像 JSON parser to encode and decode your data 这样的东西,这样你就不会因为上面提到的问题而损坏它。

祝你好运!

这是一个很好的方法。如果所有字段都是特定大小的整数类型,例如 int32_tint64_t 或 char,并且您 read/write 适当数量的 to/from 数组,您应该没问题.

您需要注意的一件事是字节顺序。任何整数类型都应该以已知的字节顺序写入,并以相关系统的正确字节顺序读回。最简单的方法是对 16 位整数使用 ntohshtons 函数,对 32 位整数使用 ntohlhtonl 函数。 64位整数没有对应的标准函数,不过写起来应该不难。

下面是如何为 64 位编写这些函数的示例:

uint64_t htonll(uint64_t val)
{
    uint8_t v[8];
    uint64_t *result = (uint64_t *)v;
    int i;

    for (i=0; i<8; i++) {
        v[i] = (uint8_t)(val >> ((7-i) * 8));
    }

    return *result;
}

uint64_t ntohll(uint64_t val)
{
    uint8_t *v = (uint8_t *)&val;
    uint64_t result = 0;
    int i;

    for (i=0; i<8; i++) {
        result |= (uint64_t)v[i] << ((7-i) * 8);
    }

    return result;
}

如果您使用 GCC 或任何其他支持 "packed" 结构的编译器,只要您避免在结构中使用 [u]intX_t 类型以外的任何类型,并在任何类型的字段中执行字节序修复大于 8 位,你是平台安全的:)

这是一个示例代码,您可以在其中获得平台之间的可移植性,不要忘记手动编辑字节顺序 UIP_BYTE_ORDER

#include <stdint.h>
#include <stdio.h>

/* These macro are set manually, you should use some automated detection methodology */
#define UIP_BIG_ENDIAN 1
#define UIP_LITTLE_ENDIAN 2
#define UIP_BYTE_ORDER UIP_LITTLE_ENDIAN

/* Borrowed from uIP */
#ifndef UIP_HTONS
#   if UIP_BYTE_ORDER == UIP_BIG_ENDIAN
#      define UIP_HTONS(n) (n)
#      define UIP_HTONL(n) (n)
#      define UIP_HTONLL(n) (n)
#   else /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */
#      define UIP_HTONS(n) (uint16_t)((((uint16_t) (n)) << 8) | (((uint16_t) (n)) >> 8))
#      define UIP_HTONL(n) (((uint32_t)UIP_HTONS(n) << 16) | UIP_HTONS((uint32_t)(n) >> 16))
#      define UIP_HTONLL(n) (((uint64_t)UIP_HTONL(n) << 32) | UIP_HTONL((uint64_t)(n) >> 32))
#   endif /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */
#else
#error "UIP_HTONS already defined!"
#endif /* UIP_HTONS */


struct __attribute__((__packed__)) s_test
{
    uint32_t a;
    uint8_t b;
    uint64_t c;
    uint16_t d;
    int8_t string[13];
};

struct s_test my_data =
{
    .a = 0xABCDEF09,
    .b = 0xFF,
    .c = 0xDEADBEEFDEADBEEF,
    .d = 0x9876,
    .string = "bla bla bla"
};

void save()
{
    FILE * f;
    f = fopen("test.bin", "w+");

    /* Fix endianness */
    my_data.a = UIP_HTONL(my_data.a);
    my_data.c = UIP_HTONLL(my_data.c);
    my_data.d = UIP_HTONS(my_data.d);

    fwrite(&my_data, sizeof(my_data), 1, f);
    fclose(f);
}

void read()
{
    FILE * f;
    f = fopen("test.bin", "r");
    fread(&my_data, sizeof(my_data), 1, f);
    fclose(f);

    /* Fix endianness */
    my_data.a = UIP_HTONL(my_data.a);
    my_data.c = UIP_HTONLL(my_data.c);
    my_data.d = UIP_HTONS(my_data.d);
}

int main(int argc, char ** argv)
{
    save();
    return 0;
}

这就是保存的文件转储:

fanl@fanl-ultrabook:~/workspace-tmp/test3$ hexdump -v -C test.bin 
00000000  ab cd ef 09 ff de ad be  ef de ad be ef 98 76 62  |..............vb|
00000010  6c 61 20 62 6c 61 20 62  6c 61 00 00              |la bla bla..|
0000001c