2009-07-09 20 views
2

我正在研究应在几个不同的操作系统和计算机上写入和读取的文件格式。其中一些计算机应该是x86机器,其他x86-64。其他一些处理器可能存在,但我不关心它们还有在C代码中写入和读取long int值

这种文件格式应该包含将被读这样几个数字:

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(这可能会使我的代码更快)吗?

回答

8

使用stdint.h中的类型可确保您获得相同数量的输入和输出字节。

然后,你只是处理排序问题,你代码可能不会 真正处理。

使用别名char *序列化long会在写入的文件中为不同字节序的平台留下不同的字节顺序。

你应该分解字节的东西,像这样:

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); 
+1

@GMan - 你不^ h同样的问题与工会(除非你有条件地编译基于平台的endianess联盟的不同定义)? – 2009-07-09 19:57:43

+0

对stdint的引用非常有用,它会有很大的帮助! – luiscubal 2009-07-09 21:29:22

1

您也可能会endianness遇到的问题。为什么不只是使用诸如NetCDFHDF之类的东西来处理可能出现的任何可移植性问题?

1

而不是使用结构与他们的字符,考虑更多的数学方法:

long l = fgetc() << 24; 
    l |= fgetc() << 16; 
    l |= fgetc() << 8; 
    l |= fgetc() << 0; 

这是一个小更直接,更清楚你要完成的任务。它也可以在循环中实现以处理更大的数字。

1

你不想使用long int。在不同的平台上可以有不同的大小,因此是平台无关格式的非启动器。您必须确定文件中需要存储的值的范围。 32位可能是最简单的。

你说你不担心其他平台还有。我将这意味着你想保留支持它们的可能性,在这种情况下,你应该定义文件格式的字节顺序。 x86是小端,所以你可能认为这是最好的。但是,如果有任何事情发生,那么big-endian就是“标准”交换订单,因为它被用于网络。

如果你去大端(“网络字节顺序”):

// 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或其他方法进行恶意优化的平台特定的字节交换。或者使用小端,假设大多数平台都是小端,所以平均而言,它们的平均速度更快。在这种情况下,你需要编写或者找到“host to little-endian”和“little-endian to host”的功能,这当然在x86上什么都不做。

0

我相信最多的交叉架构方法是使用stdint.h中定义的uintXX_t类型。 See man page here.例如,一个int32_t会在x86和x86-64上给你一个32位的整数。 我现在在我的所有代码中都使用这些代码,并且没有任何问题,因为它们在所有* NIX中都是相当标准的。

0

假设sizeof(uint32_t) == 4,有4!=24可能的字节顺序,其中little-endian和big-endian是最突出的例子,但其他的也已被使用(例如PDP-endian)。

下面是读取功能和从流写入32位无符号整数,听取其通过其表示是字节序列0,1,2,3的整数指定一个任意字节顺序:endian.hendian.c

头定义这些原型

_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