不使用reinterpret_cast读取二进制数据

10

因为我之前从未读过二进制文件,所以我写了一个程序来读取二进制STL文件。我使用ifstream的read成员函数,并将char*作为参数。为了将我的结构体转换为char*,我使用了reinterpret_cast。但就我所记得的,我读过的关于C++的每一本书都说过类似的话:“除非必须,否则不要使用reinterpret_cast”。有没有更好的方法来读取二进制数据,不一定是直接读取,但至少要在结构体中进行,而不使用reinterpret_cast?

主要功能:

std::ifstream in (cmdline[1].c_str(), std::ios::binary);

in.seekg(80, std::ifstream::beg); //skip header

int numTriangle;
in.read (reinterpret_cast<char*>(&numTriangle), sizeof(int)); //determine number of triangles
//create triangle data type and read data
triangle* t = new triangle();
for (int i = 0; i < numTriangle; ++i)  {
    in.read(reinterpret_cast<char*>(t), triangle::size);
    std::cout << *t;  // there's an opertor<< for triangle
}
delete t;

in.close(); //close file read from

还有三角形结构体

//attempt to get the right size of a class without structure padding
#pragma pack(push)
#pragma pack(1)

//standard STL triangle data structure
struct triangle {
public:
    float n[3]; //normals, 4*3=12 bytes

    float x[3]; //first point of the triangle, 4*3=12 bytes
    float y[3]; //second point of the triangle, 4*3=12 bytes
    float z[3]; //third point of the triangle, 4*3=12 bytes

    long int a; //attributes, 2 bytes

    static const int size = 12+12+12+12+2; //sum of member variables
    //static const int size = sizeof(n) + sizeof(x) + sizeof(y) + sizeof(z) + sizeof(a);
};
#pragma pack(pop)

(额外问题:#pragma pack(1) 在 cygwin 的 g++-4 上无效。我该如何确定结构体的大小?)

1
如果您正在读写二进制数据,使用reinterpret_cast是一个好主意。这是因为代码本质上是不可移植的,因此reinterpret_cast是一种很好的自我记录代码的方式,以指示代码是不可移植的。注意:您的代码没有任何问题。它是不可移植的,因为C/C++定义基本类型及其布局的方式可能因硬件/操作系统/编译器/编译器标志而异。 - Martin York
3个回答

9

嗯,那段代码看起来不错。你甚至还考虑了padding问题。我不认为你能避免在这里进行强制类型转换。你可以按照以下步骤操作:

static_cast<char*>(static_cast<void*>(t))

但实际上,在我的代码中我并没有这样做。这只是一种更加嘈杂的将reinterpret_cast直接转换为char*的方式。(参见通过void*进行强制类型转换而不是使用reinterpret_cast)。


可以使用sizeof确定结构体大小。只需在.cpp中初始化类内的static成员(然而,此时编译器不再知道::size的值,无法进行内联)。
或者,您可以将其编写为静态内联成员函数。在其主体中,类类型被视为完整的,并且允许使用sizeof(triangle)。或者您可以像注释中那样使用sizeof,但使用类型而不是成员(只有在C++0x中才允许以这种方式引用非静态成员):

//standard STL triangle data structure
struct triangle {
public:
    float n[3]; //normals, 4*3=12 bytes

    float x[3]; //first point of the triangle, 4*3=12 bytes
    float y[3]; //second point of the triangle, 4*3=12 bytes
    float z[3]; //third point of the triangle, 4*3=12 bytes

    long int a; //attributes, 2 bytes

    static int size() { return sizeof(triangle); } // this way
    static const int size = sizeof(float[3])*4 + sizeof(long int); // or this way
};

然而,第二种方法并不好,因为当你添加成员时很容易忘记更新它。


谢谢。我忘记删除第二行了,那天写这个时已经很晚了。然而,我的cygwin的gcc/g++/...似乎出了问题,无论是sizeof,还是sizeof+pragma pack,还是sizeof attribute((pack))都不起作用。 - DaClown
1
请注意,像这样进行状态序列化本质上是不可移植的 - 尝试在不同架构(甚至是使用不同编译器的相同架构)上读取文件可能会导致垃圾数据恢复。此外,我很久没有看到 long 为两个字节的系统了 - 最有可能是四个或八个字节,具体取决于处理器和编译器设置。编辑:你说这些东西不起作用是什么意思?你尝试过删除建议的 pragma pack 和静态大小函数吗? - Mark B
事实上,在任何具有8位字节的系统上,long必须大于2个字节。 - Johannes Schaub - litb
你可以将 size 的计算移动到一个单独的 trait 类中。这样,你就可以同时获得两种方法的优点,并从类本身中删除序列化的关注点。 - MSN
是的,这个“long”就是缺失的一环。当然应该是一个“short int”。谢谢。 - DaClown

2

额外问题:请看__attribute__((packed))


-2

在我看来,使用流进行文件I/O(特别是二进制)是非常糟糕的。如果是我,我宁愿使用像fopen和fread这样的旧的C函数。

此外,内存映射文件是一种技术,在我看来并没有得到足够的关注。我不知道有哪些标准/可移植库支持它,但如果你在使用Windows系统,我建议查看this MSDN article


2
有任何实质性的证据吗?为什么标准的C++函数“讨厌”?它们缺少什么?古老的C版本做得更好吗?我非常怀疑。我使用C++ fstreams读取经过精心映射的结构体文件并进行各种位精确的操作时没有遇到任何问题。它们的面向对象、RAII、异常处理等优点非常显著。这更像是一个主观的抱怨,而不是一个可用的答案。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接