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

存储C结构以供多平台使用-这种方法有效吗?,c,struct,cross-platform,padding,C,Struct,Cross Platform,Padding,编译器:GNU GCC 应用程序类型:控制台应用程序 语言:C 平台:Win7和Linux Mint 我写了一个我想在Win7和Linux下运行的程序。该程序将C结构写入文件,我希望能够在Win7下创建该文件,并在Linux中读取它,反之亦然 到目前为止,我已经了解到,使用fwrite()编写完整的结构将几乎100%地保证它不会被其他平台正确读回。这可能是由于填充和其他原因造成的 我自己定义了所有的结构,它们(现在,在我在这个论坛上的上一个问题之后)都有int32\u t、int64\u t和c

编译器:GNU GCC

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

语言:C

平台:Win7和Linux Mint

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

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

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

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


感谢阅读

将数据按元素写入文件是最好的方法,因为
struct
s将

然而,即使使用您计划使用的方法,仍然存在潜在的陷阱,例如系统之间的差异或不同(即:)

如果你要这么做,你应该考虑一些事情,这样你不会因为上面提到的问题而损坏它。
祝你好运

这是一个好方法。如果所有字段都是特定大小的整数类型,例如
int32\u t
int64\u t
或char,并且您在数组中读取/写入了适当数量的字段,则应该可以

你需要注意的一件事是持久性。任何整数类型都应以已知的字节顺序写入,并以相关系统的正确字节顺序读回。最简单的方法是对16位整数使用
ntohs
htons
函数,对32位整数使用
ntohl
htonl
函数。对于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;
}
uint64\u t htonll(uint64\u t val)
{
uint8_t v[8];
uint64_t*结果=(uint64_t*)v;
int i;
对于(i=0;i>((7-i)*8));
}
返回*结果;
}
uint64_t ntohll(uint64_t val)
{
uint8_t*v=(uint8_t*)&val;
uint64_t结果=0;
int i;

对于(i=0;i如果您使用GCC或任何其他支持“打包”结构的编译器,只要您避免在结构中使用除
[u]intX\t
类型以外的任何类型,并且在类型大于8位的任何字段中执行endianness fix,您就是平台安全的:)

这是一个示例代码,您可以在平台之间进行移植,不要忘记手动编辑endianness
UIP\u BYTE\u顺序

#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;
}

我会让编译器在这里展开循环。如果启用截断错误,您的代码将生成警告。对于序列化,而不是掩蔽,转换为
uint8\u t
。对于反序列化,移位将不会按预期工作,因为
v[I]
将被提升为
int
。因此,对于32位
int
,例如移位>=32会调用未定义的行为,对于某些值也是24位移位。但是这样的想法是正确的。对于
ntohll
我会传递一个
常量字符[8]
;不知道为什么要在这里强制转换。
uint64\u t
会产生问题,实际上是无用的。通过循环,您可以使这两个函数都通用(将值作为
uint64\u t
传递,但允许转换1..8个八位字节)。@Olaf,您的意思是ntohll()头应该是“uint64\u t ntohll(const char[8])?@Marnix:是的。只需将缓冲区的一部分传递给函数(当然,您不需要第一行)。并添加另一个参数,其大小和使用方式为loop-max。允许在每个方向上仅使用一个函数来处理所有宽度。我进一步研究了Endian的问题。这可能是一个愚蠢的问题,但我还是会尝试。这个大/小Endian似乎归结为颠倒整数中的字节顺序。如果我只写一个functi呢打开以交换字节顺序(07、16等)。因此,如果我的写入程序检测到其具有“错误”的Endian格式,它将在写入文件之前交换所有整数。同样,如果读取程序检测到其位于错误的Endian平台上,它将在从文件读取后交换所有整数。如果平台具有正确的Endian,则它们不会执行任何操作。我是否遗漏了某些内容?而一般来说,您是对的,gcc只支持2s补码。从有符号到无符号的转换是我们定义的。OP明确表示他不需要外部库。@Olaf True,OP确实要求没有外部库,但另一种选择是从头编写解析器。如果是这样,OP最好只将信息存储为ASCII编码的文本字符串。因此,许多问题似乎遵循这种格式
我需要功能XYZ,没有任何外部库我如何做
,最终结果是,
只需使用库
,或者
在重新发明轮子时准备大量工作。
。即使OP想要编写解析器,上面提供的信息也应该一路上帮助他/她。不需要使用文本格式。如果定义和实现正确,二进制格式将是可移植的。请参阅@dbush的答案。将结构转换为标准XML或JSON以实现最终的可移植性…只是一个想法。打包的
struct
s是非标准的。正确的序列化是更可移植的方法。@fanl,我是试图理解这段代码。注释行#else/*UIP\u BYTE\u ORDER==UIP\u BIG\u ENDIAN*/是否应该读取
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