将C++中的bitset写入二进制文件

3
希望有人能帮忙。我的问题如下:
我正在创建包含二进制数据的文件。每个文件开头都有一个二进制标头,其中包含有关文件内容的信息。文件标头是固定大小的,为52字节。标头在标头内具有特定的信息,这些信息位于标头内的特定字节偏移量处,但某些信息仅覆盖部分字节,例如3位。
例如:
Byte 1-4 = 文件长度 Byte 5-8 = 标头长度 Byte 8-9 = 版本信息 Byte 10-13 = 文件创建时间戳
位1-4 = 月份(1-12) 位5-9 = 日(1-31) 位10-14 = 小时(0-23) 位15-20 = 分钟(0-59) 位21 = UTC偏移方向 位22-26 = UTC偏移小时 位27-32 = UTC偏移分钟
一些值是静态定义的,一些则在运行时确定。我尝试创建标头的“映射”,定义属性必须使用的位数以及所表示的值。这些存储在int对的向量中,int_pair.first是值,int_pair.second是位数。然后,我将提供的值(全部为整数)转换为二进制格式,并将二进制符号插入stringstream中。然后,我从二进制值的字符串表示中创建一个bitset,并将其写入文件。我的问题是,字节未按正确顺序显示在输出文件中。
我将省略获取值的方法,仅在我的示例中提供整数,并为简洁起见截断标头中的某些信息(因此在此示例中,标头为14个字节,而不是52个字节),但这大致是我正在做的:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <bitset>
#include <vector>
#include <algorithm>

int main ()
{
    vector<pair<int,int>> header_vec;

    header_vec.push_back(make_pair(9882719,32)); // file length
    header_vec.push_back(make_pair(52,32)); // header length
    header_vec.push_back(make_pair(6,3)); // high release identifier
    header_vec.push_back(make_pair(4,5)); // high version identifier
    header_vec.push_back(make_pair(6,3)); // low release identifier
    header_vec.push_back(make_pair(4,5)); // low version identifier

    // file open timestamp
    header_vec.push_back(make_pair(9,4));  // month
    header_vec.push_back(make_pair(6,5));  // day
    header_vec.push_back(make_pair(19,5)); // hour
    header_vec.push_back(make_pair(47,6)); // min
    header_vec.push_back(make_pair(0,1));  // utc direction
    header_vec.push_back(make_pair(0,5));  // utc offset hours
    header_vec.push_back(make_pair(0,6));  // utc offset minutes

    ostringstream oss;

    // convert each integer to binary representation
    for ( auto i : header_vec )
    {
        for (unsigned int j(i.second-1); j != -1; --j)
        {
            oss << ((i.first &(1 << j)) ? 1 : 0);
        }
    }

    // copy oss
    string str = oss.str();

    // create bitset
    bitset<112> header_bits(string(str.c_str()));

    // write bitset to file
    ofstream output("header.out", ios::out | ios::binary );
    output.write( reinterpret_cast<char *>(&header_bits), 14);
    output.close();

    return 0;

}

现在,大多数情况下这种方法似乎是可行的,除了比特位需要反转。如果我在fm中查看输出文件,我希望看到以下内容:

File: header.out    (0x0e bytes)
Byte: 0x0

00    00 96 cc 5f 00 00 00 34 c4 c4 93 4e f0 00           ..._...4...N...O

      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f      0123456789abcdef

实际上,我看到的是这样的:
File: header.out    (0x0e bytes)
Byte: 0x0

00    00 f0 4e 93 c4 c4 34 00 00 00 5f cc 96 00           @O...N...4..._..

      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f      0123456789abcdef

在创建位集之前,我尝试翻转字符串,但这也不能产生期望的输出。

我想我对位集的理解还不够,无法意识到为什么会发生这种情况。非常感谢任何和所有的建议!如果有其他实现方法,请分享!

提前致谢... -J


向量(vector)有 push_front 吗?我得到了编译错误。test.cpp: 在函数 'int main()' 中: test.cpp:65:16: 错误:'class std::vector<std::pair<int, int>>' 没有名为 'push_front' 的成员。 - JakobWithAK
啊,遗憾地是不行。抱歉 - 但其他集合类可以。 - Alex Brown
1
你为什么要“转换为二进制表示法”?变量和常量已经以二进制形式存储了。大部分情况下,你只需要执行ofstream.write调用即可。唯一有些棘手的是日期/时间戳,但这可以通过一些移位运算来完成。而且,reinterpret_cast不是有些不安全的代码吗? - nicholas
请查看这个问题的答案:https://dev59.com/lEfRa4cB1Zd3GeqP70_u - Vaughn Cato
2个回答

1

直接将bitset<>写成内存转储形式肯定是不可移植的,这就需要使用reinterpret_cast<>。换句话说,即使数据以一个漂亮的块布局,你也不知道它是如何完成的。

如果我是你,我会编写一个更简单的函数来从位集中提取8位的块,并使用正常访问运算符[]将它们作为字节写入文件。

至于另一种方法,当我想要读/写二进制文件时,我通常会定义一个结构或一组结构,直接映射到文件的布局。

例如:

struct Timestamp
{
    int month:4;
    int day:5;
    int hour:5;
    int minute:6;
    int utcOffsetDirection:1;
    int utcOffsetHour:5;
    int utcOffsetMinute:5;

};

我完全同意。掌控字节序和打包规则,通过逐个管理字节来掌控局面。从长远来看,你不会后悔的。 - WhozCraig
Keith和Andre,这种方法对我来说很有道理。今晚我没时间,但我明天会尝试。谢谢。 - JakobWithAK
当实际编写代码时,请执行output.write((char*) &myTimeStamp, 4);,但请注意不要使用sizeof运算符,以避免填充问题。 - nicholas

1
为什么不直接使用 位域结构体,这样你就可以直接读写结构体,而无需担心“位解析”。只需注意内存对齐即可,并确保添加一些填充以适应字边界。
struct timestamp{
       unsigned mont:4;
       unsigned day:5;
       unsigned hour:5;
       unsigned minute:6;
       unsigned utc:1;
       unsigned utc_hour:5;
       unsigned utc_min:6   
};


struct header{
   int32_t file_length;
   int32_t header_lenght;
   int16_t version;
   timestamp tmsp;
};

你的代码快速修复:除了时间戳之外,所有字节大小都是字节大小,唯一的位域是时间戳参数。此外,顺序应该被反转(在 MS 平台上)。 - nicholas
@nicholas 谢谢,我在回答问题时太急了,没有意识到位域可以完成这项工作。我没有注意到一切都是位。 - André Oriani
@nicholas你说“此外,在MS平台上,顺序应该被颠倒”。你是在谈论字节序吗? - André Oriani
Microsoft文档中让我有点困惑。现在我认为在这种情况下顺序不需要被颠倒。(当时我们工作得很晚)至于字节序:我的猜测是它没有影响:ofstream.write逐字节解析(char*),而字节序是在写入多字节对象时需要考虑的问题...只有测试才能说明 - 我已经好几年没做C++程序员了,现在用的是C#。 - nicholas

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