存储C结构以供多平台使用 - 这种方法是否有效?

时间:2016-01-11 18:31:29

标签: c struct cross-platform padding

编译器:GNU GCC

应用程序类型:控制台应用程序

语言:C

平台:Win7和Linux Mint

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

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

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

这种方法有用吗?我更喜欢对源代码进行最大程度的控制,所以除非我真的需要,否则我不会寻找库或其他依赖项来实现这一点。

感谢您阅读

3 个答案:

答案 0 :(得分:1)

以数据方式将数据写入文件是最佳方法,因为structdiffer due to alignment and packing differences between compilers.

然而,即使使用您计划使用的方法,仍然存在潜在的缺陷,例如系统之间的endianness不同,或encoding schemes不同(即:two's complement versus one's complement encoding of signed numbers)。

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

祝你好运!

答案 1 :(得分:1)

如果您使用GCC或任何其他支持"打包"结构,只要你避免在结构中使用除[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

答案 2 :(得分:0)

这是一个很好的方法。如果所有字段都是特定大小的整数类型,例如int32_tint64_t或char,并且您在数组中读取/写入适当数量的字段,那么您应该没问题。

你需要注意的一件事是字节序。任何整数类型都应以已知的字节顺序写入,并以相应的字节顺序读回相关系统。最简单的方法是使用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;
}