C++ 通用而非瞬时处理endianness
我所读取的文件的格式在不同平台上是一致的,但可能是大的或小的endian,这取决于构建文件的平台。所述平台由文件中的值定义C++ 通用而非瞬时处理endianness,c++,endianness,C++,Endianness,我所读取的文件的格式在不同平台上是一致的,但可能是大的或小的endian,这取决于构建文件的平台。所述平台由文件中的值定义 // Designed as a drop-in replacement for an ifstream. // (Non-public inheritance *might* be appropriate if you want to restrict the interface.) class IFile : public std::ifstream { private
// Designed as a drop-in replacement for an ifstream.
// (Non-public inheritance *might* be appropriate if you want to restrict the interface.)
class IFile : public std::ifstream {
private:
File::Endian endianness;
public:
// Mimic the constructors of std::ifstream that you need.
explicit IFile(const std::string & filename);
// It should be possible to use some template magic to simplify the
// definition of these three functions, but since there are only three:
void ReadBytes(uint16_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ushort(data);
}
}
void ReadBytes(uint32_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ulong(data);
}
}
void ReadBytes(uint64_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_uint64(data);
}
}
};
目前,我处理endianness的方式是使用if语句,一个正常读取文件,另一个使用:
//source.cpp
#包括“source.h”
#包括
std::ifstream文件;
文件::Endian endianness;
//…删除。。。
bool GetPlatform(){
uint32_t平台;
file.read(reinterpret_cast(&platform),sizeof(platform));
如果(平台==1){
endianness=File::Endian::Little;
}
else if(platform==2我认为通常的答案是#ifdef函数的定义,如read64:
int64_t read64(char *pos) {
#ifdef IS_BIG_ENDIAN
...
#elif IS_LITTLE_ENDIAN
...
#else
// probably # error
#endif
}
“自动”读取正确的尾数的唯一方法是让CPU的本机尾数与文件中字节的尾数匹配。如果它们不匹配,那么代码中需要知道一些事情,以便执行必要的字节交换,从文件的尾数到CPU的尾数(从文件读取时),反之亦然(写入文件时)
查看如何实现和处理网络订单(也称为big-endian)数据——在big-endian平台(如PowerPC)上,它们是简单的无操作,逐字返回其参数。在little-endian平台上(如Intel)通过这种方式,调用它们的代码不必在运行时进行任何条件测试来确定字节交换是否合适,它只需无条件地运行通过ntohl()
或ntohs()读取的所有数据
,并相信他们会对所有平台上的数据做正确的处理。同样,在写入数据时,它会无条件地通过htonl()
或htons()
运行所有数据值,然后再将数据发送到文件/网络/任何地方
您的程序可以通过调用这些实际函数,或(如果您需要读取的数据类型多于16位和/或32位整数)通过查找或编写与精神上类似的函数来执行类似操作,例如:
inline uint32_t NativeToLittleEndianUint32(uint32_t val) {...}
inline uint32_t LittleEndianToNativeUint32(uint32_t val) {...}
inline uint32_t NativeToBigEndianUint32(uint32_t val) {...}
inline uint32_t BigEndianToNativeUint32(uint32_t val) {...}
[...]
inline uint64_t NativeToLittleEndianUint64(uint64_t val) {...}
inline uint64_t NativeToBigEndianUint64(uint64_t val) {...}
inline uint64_t LittleEndianToNativeUint64(uint64_t val) {...}
inline uint64_t BigEndianToNativeUint64(uint64_t val) {...}
[...]
…等等。代码中数千个if/then子句都消失了,取而代之的是编译时条件逻辑。这使得代码更高效、更易于测试,并且更不容易出错。如果您喜欢模板化函数,可以使用它们来减少调用代码编写器需要的函数名数量记住(例如,您可以使用带有模板覆盖的内联模板nativetolittledian(T val){…}
对所有需要支持的类型执行正确的操作)
如果您想更进一步,可以将读/写和字节交换函数组合到一个更大的函数中,从而避免对每个数据值进行两次函数调用
注意:在为浮点类型实现这些函数时要小心;某些CPU架构(如Intel)将隐式修改意外的浮点位模式,这意味着,例如,当endian交换32位浮点值时,需要将该值的非本机/外部/字节交换表示形式存储为uint32,而不是“浮点”。如果您想查看我在代码中如何处理此问题的示例,请查看例如B_HOST_to_BENDIAN_iflot
和B_BENDIAN_to_HOST_iflot
中的宏的定义。如果我了解您的情况,您的基本问题是缺乏抽象级别。您有一组函数可以读取各种数据一个文件的结构。由于这些函数直接调用std::ifstream::read
,它们都需要知道它们正在读取的结构和文件的布局。这是两个任务,比理想的任务多了一个。最好将此逻辑分为两个抽象级别。让我们为新的l调用函数evelReadBytes
,因为它们专注于从文件中获取字节。由于Microsoft提供了三个byteswap intrinsic,因此将有三个这样的函数。下面是对4字节值的第一个尝试
void ReadBytes(std::ifstream & file, File::Endian endianness, uint32_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ulong(data);
}
}
使用这个小样本代码,节省的成本并不明显。但是,您指出可能有许多函数填补了ReadData
的角色。这种方法将纠正endianness的负担从这些函数转移到新的ReadBytes
函数。if
语句的数量会减少从“几百,如果不是几千”减少到三
这一变化是由一个经常被称为“不要重复你自己”的编程原则推动的。同样的原则也会引发诸如“为什么不止一个函数需要这段代码?”
另一个使问题复杂化的问题是,您似乎对问题采取了过程性方法,而不是面向对象的方法。过程性方法的症状可能包括过多的函数参数(例如,endianness
作为参数)和全局变量。如果将接口包装在类中,则该接口将更易于使用。下面是声明此类的开始(即头文件的开始)。请注意,endianness是私有的,并且此标头没有指示如何确定endianness。如果封装良好,则此类之外的代码将不关心是哪个平台创建了该文件
// Designed as a drop-in replacement for an ifstream.
// (Non-public inheritance *might* be appropriate if you want to restrict the interface.)
class IFile : public std::ifstream {
private:
File::Endian endianness;
public:
// Mimic the constructors of std::ifstream that you need.
explicit IFile(const std::string & filename);
// It should be possible to use some template magic to simplify the
// definition of these three functions, but since there are only three:
void ReadBytes(uint16_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ushort(data);
}
}
void ReadBytes(uint32_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ulong(data);
}
}
void ReadBytes(uint64_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_uint64(data);
}
}
};
此时,各种ReadData
函数可能如下所示(不使用全局变量)
这比您要查找的代码更简单,因为重复的代码更少。(转换为char*
,获得大小不再需要到处重复。)
总而言之,有两个主要方面需要改进
不要重复你自己。重复的代码
void ReadBytes(std::ifstream & file, File::Endian endianness, uint32_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ulong(data);
}
}
void ReadData() {
uint32_t data;
ReadBytes(file, endianness, data);
// More processing here, maybe more reads.
}
// Designed as a drop-in replacement for an ifstream.
// (Non-public inheritance *might* be appropriate if you want to restrict the interface.)
class IFile : public std::ifstream {
private:
File::Endian endianness;
public:
// Mimic the constructors of std::ifstream that you need.
explicit IFile(const std::string & filename);
// It should be possible to use some template magic to simplify the
// definition of these three functions, but since there are only three:
void ReadBytes(uint16_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ushort(data);
}
}
void ReadBytes(uint32_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_ulong(data);
}
}
void ReadBytes(uint64_t & data) {
file.read(reinterpret_cast<char*>(&data), sizeof(data));
if (endianness == File::Endian::Big) {
data = _byteswap_uint64(data);
}
}
};
// Helper function, not needed outside this class.
// This should be either static or put into an anonymous namespace.
static File::Endian ReadEndian(std::ifstream & file) {
uint32_t platform;
file.read(reinterpret_cast<char*>(&platform), sizeof(platform));
if (platform == 1) {
return File::Endian::Little;
}
else if (platform == 2 << 24) {
return File::Endian::Big;
}
// Handle unrecognized platform here
}
IFile::IFile(const std::string & filename) : std::ifstream(filename),
endianness(ReadEndian(file))
{}
void ReadData(IFile & file) {
uint32_t data;
file.ReadBytes(data);
}