为多个平台使用存储 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 将数据写入文件是最好的方法,因为 struct
s 将 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_t
、int64_t
或 char,并且您 read/write 适当数量的 to/from 数组,您应该没问题.
您需要注意的一件事是字节顺序。任何整数类型都应该以已知的字节顺序写入,并以相关系统的正确字节顺序读回。最简单的方法是对 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;
}
如果您使用 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
编译器: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 将数据写入文件是最好的方法,因为 struct
s 将 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_t
、int64_t
或 char,并且您 read/write 适当数量的 to/from 数组,您应该没问题.
您需要注意的一件事是字节顺序。任何整数类型都应该以已知的字节顺序写入,并以相关系统的正确字节顺序读回。最简单的方法是对 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;
}
如果您使用 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