题
我正在研究一种应该在几种不同的操作系统和计算机中写入和读取的文件格式。其中一些计算机应该是 x86 计算机,其他计算机应该是 x86-64。可能存在其他一些处理器,但我不关心它们 然而.
该文件格式应包含几个数字,可按如下方式读取:
struct LongAsChars{
char c1, c2, c3, c4;
};
long readLong(FILE* file){
int b1 = fgetc(file);
int b2 = fgetc(file);
int b3 = fgetc(file);
int b4 = fgetc(file);
if(b1<0||b2<0||b3<0||b4<0){
//throwError
}
LongAsChars lng;
lng.c1 = (char) b1;
lng.c2 = (char) b2;
lng.c3 = (char) b3;
lng.c4 = (char) b4;
long* value = (long*) &lng;
return *value;
}
并写为:
void writeLong(long x, FILE* f){
long* xptr = &x;
LongAsChars* lng = (LongAsChars*) xptr;
fputc(lng->c1, f);
fputc(lng->c2, f);
fputc(lng->c3, f);
fputc(lng->c4, f);
}
虽然这似乎在我的计算机上有效,但我担心在其他计算机上可能无效,或者文件格式最终可能会因计算机而异(例如,32 位计算机与 64 位计算机)。难道我做错了什么?我应该如何实现我的代码以使用每个数字的恒定字节数?
我应该只使用 fread (这也可能使我的代码更快)吗?
解决方案
使用类型stdint.h
,以确保您获得相同的字节数和缩小。
然后你只留下处理排列顺序问题,你的代码可能不 真处理。
序列化的长与混迭的char *让你在与不同的字节序的平台的书面文件不同的字节顺序。
您应该分解字节的东西,像这样:
char c1 = (val >> 0) & 0xff;
char c2 = (val >> 8) & 0xff;
char c3 = (val >> 16) & 0xff;
char c4 = (val >> 24) & 0xff;
,重新然后使用类似:
val = (c4 << 24) |
(c3 << 16) |
(c2 << 8) |
(c1 << 0);
其他提示
不是使用结构与它们的字符,考虑更数学方法:
long l = fgetc() << 24;
l |= fgetc() << 16;
l |= fgetc() << 8;
l |= fgetc() << 0;
这是一个小更直接,更清楚你要完成的任务。它也可以在一个循环中以处理更大的数字实现。
您不想使用长整型。这可以在不同的平台不同的尺寸,所以是一个非首发的平台无关的格式。你必须决定需要被存储在文件在什么范围内的值。 32位可能是最容易的。
您说你是不是担心其他平台的尚未的。我认为这意味着要保留的支持他们的可能性,在这种情况下,你应该定义文件格式的字节顺序。 86是小端,所以你可能会认为这是最好的。但大端是“标准”交换顺序如果有什么,因为它在网络中使用。
如果你去大端(“网络字节顺序”):
// can't be bothered to support really crazy platforms: it is in
// any case difficult even to exchange files with 9-bit machines,
// so we'll cross that bridge if we come to it.
assert(CHAR_BIT == 8);
assert(sizeof(uint32_t) == 4);
{
// write value
uint32_t value = 23;
const uint32_t networkOrderValue = htonl(value);
fwrite(&networkOrderValue, sizeof(uint32_t), 1, file);
}
{
// read value
uint32_t networkOrderValue;
fread(&networkOrderValue, sizeof(uint32_t), 1, file);
uint32_t value = ntohl(networkOrderValue);
}
事实上,你甚至都不需要声明两个变量,它只是一个有点混乱,以取代“价值”与它的网络秩序等同于同一个变量。
它的工作原理,因为“网络字节顺序”被定义为在存储器中的可更换(大端)序位的结果的任何布置。没有必要惹工会因为在C任何存储的对象可以被视为字符的序列。无需特殊情况的字节顺序,因为这是再用ntohl / htonl是。
如果这是太慢了,你可以开始考虑恶魔般的优化平台特定字节交换,与SIMD或什么的。或者用小端,在假设大部分的平台将是little-endian的,所以它是碰到他们“平均”更快。在这种情况下,你需要编写或找到“主机小端”和“小尾数举办”功能,这当然在x86的只是什么也不做。
相信大多数跨架构的方法是使用所述uintXX_t类型,如在stdint.h定义。 看到这里的人页。例如,一个int32_t会给你在x86 32位整数和x86-64。 我默认情况下,所有的我的代码现在使用这些暂时还没有烦恼,因为它们是所有* NIX相当标准。
假设 sizeof(uint32_t) == 4
, , 有 4!=24
可能的字节顺序,其中小端和大端是最突出的例子,但也使用了其他字节顺序(例如PDP 字节序)。
以下是从流中读取和写入 32 位无符号整数的函数,注意由表示字节序列的整数指定的任意字节顺序 0,1,2,3
: 字节序.h, 字节序
标头定义了这些原型
_Bool read_uint32(uint32_t * value, FILE * file, uint32_t order);
_Bool write_uint32(uint32_t value, FILE * file, uint32_t order);
和这些常数
LITTLE_ENDIAN
BIG_ENDIAN
PDP_ENDIAN
HOST_ORDER