C++中是否有二进制内存流

Are there binary memory streams in C++

提问人:FireAphis 提问时间:10/13/2009 最后编辑:FireAphis 更新时间:11/13/2023 访问量:73904

问:

我通常用于写入内存中的字符串。有没有办法在二进制模式下写入 char 缓冲区?请考虑以下代码:stringstream

stringstream s;
s << 1 << 2 << 3;
const char* ch = s.str().c_str();

内存如下所示: 0x313233 - 字符 1、2 和 3 的 ASCII 代码。我正在寻找一种编写二进制值本身的方法。也就是说,我想0x010203在记忆中。问题是我希望能够编写一个函数ch

void f(ostream& os)
{
    os << 1 << 2 << 3;
}

并决定在外面使用什么样的流。像这样的东西:

mycharstream c;
c << 1 << 2 << 3; // c.data == 0x313233;
mybinstream b;
b << 1 << 2 << 3; // b.data == 0x010203;

有什么想法吗?

C++ IOstream

评论

1赞 jrockway 10/13/2009
那是十六进制,而不是二进制。不过,为什么你不能写0x01、0x02等......毕竟,这些是实际的 ASCII 字符。
3赞 KeithB 10/13/2009
他希望内存的内容(实际字节)是0x010203(66051十进制),而不是字符串“0x010203”。
1赞 FireAphis 10/13/2009
我已经修改了问题。希望现在更清楚了。
3赞 isekaijin 1/12/2013
好问题。太糟糕了,不可能给出一个好的答案,因为这是标准库中的设计错误。

答:

5赞 Lukáš Lalinský 10/13/2009 #1

好吧,只使用字符,而不是整数。

s << char(1) << char(2) << char(3);
49赞 KeithB 10/13/2009 #2

若要在流(包括 stringstreams)中读取和写入二进制数据,请使用 read() 和 write() 成员函数。所以

unsigned char a(1), b(2), c(3), d(4);
std::stringstream s;
s.write(reinterpret_cast<const char*>(&a), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&b), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&c), sizeof(unsigned char));
s.write(reinterpret_cast<const char*>(&d), sizeof(unsigned char));

s.read(reinterpret_cast<char*>(&v), sizeof(unsigned int)); 
std::cout << std::hex << v << "\n";

这在我的系统上。0x4030201

编辑: 为了使插入和提取运算符(<< 和 >>)透明地工作,您最好创建一个执行正确操作的派生 streambuf,并将其传递给您想要使用的任何流。

评论

0赞 FireAphis 10/13/2009
它肯定回答了问题的第一部分,但是有没有办法使插入看起来始终相同(即 s << a),但内部数据表示形式因流的类型而异?
0赞 7/17/2010
你自己的 streambuf 不能这样做;格式化是在(非虚拟)iStream 和 Ostream 方法中完成的,其结果是 Streambuf 所看到的。
0赞 MSalters 1/13/2017
该问题实际上显示了内存中的结果,而这可能会产生(假设)。0x0102030x00000001 0x00000002 0x00000003sizeof(int)==4
0赞 KeithB 1/14/2017
@MSalters 你说得对,显然比我小 6 岁的我是个白痴。
4赞 Jean Daniel Pauget 3/16/2013 #3

重载一些不寻常的运算符效果很好。在下面,我选择重载 <= 是因为它具有与 << 相同的从左到右的关联性,并且在某种程度上具有接近的外观和感觉......

#include <iostream>
#include <stdint.h>
#include <arpa/inet.h>

using namespace std;

ostream & operator<= (ostream& cout, string const& s) {
    return cout.write (s.c_str(), s.size());
}
ostream & operator<= (ostream& cout, const char *s) {
    return cout << s;
}
ostream & operator<= (ostream&, int16_t const& i) {
    return cout.write ((const char *)&i, 2);
}
ostream & operator<= (ostream&, int32_t const& i) {
    return cout.write ((const char *)&i, 4);
}
ostream & operator<= (ostream&, uint16_t const& i) {
    return cout.write ((const char *)&i, 2);
}
ostream & operator<= (ostream&, uint32_t const& i) {
    return cout.write ((const char *)&i, 4);
}

int main() {
    string s("some binary data follow : ");

    cout <= s <= " (machine ordered) : " <= (uint32_t)0x31323334 <= "\n"
         <= s <= " (network ordered) : " <= htonl(0x31323334) ;
    cout << endl;

    return 0;
}

有几个缺点:

  • <= 的新含义可能会让读者感到困惑或导致意想不到的结果:

    cout <= 31 <= 32;
    

    不会给出与

    cout <= (31 <= 32);
    
  • 在阅读代码时没有明确提到 endianess,因为 如上例所示。

  • 它不能简单地与<<混合,因为它不属于 同一组优先级。我通常用括号来澄清这样的 如:

    ( cout <= htonl(a) <= htonl(b) ) << endl;
    

评论

5赞 cubuspl42 7/10/2014
这是一个很酷的概念证明,但请注意,C++的重载运算符被认为是邪恶的,因为它们允许这样做。非明显的过载是合理的,因为它是一个标准的过载。不应发明新的黑客重载,并且应非常小心地使用重载本身。<<
2赞 kamikaze 1/13/2017 #4

对于这个用例,我为自己实现了一个“原始移位运算符”:

template <typename T, class... StreamArgs>
inline std::basic_ostream<StreamArgs...> &
operator <= (std::basic_ostream<StreamArgs...> & out, T const & data) {
        out.write(reinterpret_cast<char const *>(&data), sizeof(T));
        return out;
}

把它放在方便的地方,像这样使用:

std::cout <= 1337 <= 1337ULL <= 1337. <= 1337.f;

优势:

  • 可链接
  • 自动sizeof()
  • 也采用数组和结构/类实例

弊:

  • 对非 POD 对象不安全:泄漏指针和填充
  • 输出是特定于平台的:填充、endianess、整数类型
13赞 Samuel Powell 2/6/2018 #5

你可以用模板做这种事情。例如:

//struct to hold the value:
template<typename T> struct bits_t { T t; }; //no constructor necessary
//functions to infer type, construct bits_t with a member initialization list
//use a reference to avoid copying. The non-const version lets us extract too
template<typename T> bits_t<T&> bits(T &t) { return bits_t<T&>{t}; }
template<typename T> bits_t<const T&> bits(const T& t) { return bits_t<const T&>{t}; }
//insertion operator to call ::write() on whatever type of stream
template<typename S, typename T>
S& operator<<(S &s, bits_t<T> b) {
    return s.write((char*)&b.t, sizeof(T));
}
//extraction operator to call ::read(), require a non-const reference here
template<typename S, typename T>
S& operator>>(S& s, bits_t<T&> b) {
    return s.read((char*)&b.t, sizeof(T));
}

它可能需要一些清理,但它是功能性的。例如:

//writing
std::ofstream f = /*open a file*/;
int a = 5, b = -1, c = 123456;
f << bits(a) << bits(b) << bits(c);

//reading
std::ifstream f2 = /*open a file*/;
int a, b, c;
f >> bits(a) >> bits(b) >> bits(c);

评论

1赞 陈家胜 4/20/2018
我更喜欢这个答案,因为它不会令人困惑,而且它还可以包装其他东西,比如vector<float>
0赞 Goblinhack 9/8/2019
嘿,@SamuelPowell我非常喜欢这种方法,所以我更进一步,在这种方法的基础上编写了更多的序列化器。我喜欢这个,因为与其他 C++ 序列化程序相比,它的复杂性非常低。如果有兴趣,请查看 github.com/goblinhack/simple-c-plus-plus-serializer - 会对您的评论感兴趣。我发现由于(我认为)运算符重载问题,我不得不删除模板中的流类型。无论如何,它适用于许多类型。
1赞 mach6 9/6/2021 #6
#include <sstream>

class bostringstream {
public:
  bostringstream() : oss() {}

  template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                bool>::type = true>
  bostringstream& operator<<(const T& v) {
    oss.write((char*)&v, sizeof(T));
    return *this;
  }

  template <typename T, typename std::enable_if<
                            std::is_fundamental<typename T::value_type>::value,
                            bool>::type = true>
  bostringstream& operator<<(const T& v) {
    oss.write((char*)v.data(), v.size() * sizeof(typename T::value_type));
    return *this;
  }

  template <typename _InputIterator>
  bostringstream& write(_InputIterator first, _InputIterator last) {
    char* data = (char*)&(*first);
    auto n = std::distance(first, last);
    oss.write(data, n * sizeof(*first));
    return *this;
  }

  template <typename T, typename std::enable_if<std::is_fundamental<T>::value,
                                                bool>::type = true>
  bostringstream& write(const T* v, std::streamsize count) {
    oss.write((char*)v, sizeof(T) * count);
    return *this;
  }

  auto rdbuf() const { return oss.rdbuf(); }

  auto str() const { return oss.str(); }

  std::size_t size() { return oss.tellp(); }

protected:
  std::ostringstream oss;
};

例:

#include <array>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include "bsstream.hpp"

int main(int argc, char **argv) {

  int i = 1;
  float j = 1.1;
  double k = 1.2;
  std::vector<int> ii{1,2};
  std::vector<double> jj{1.2,2.2};
  std::string kk = "abcd";
  std::array<int, 2> ll{3,4};
  int l[] = {1,2};

  bostringstream of;
  of << i << j <<k;
  of <<ii << jj << kk << ll;
  of.write(l, 2);

  std::ofstream oof("foo.bin", std::ios::binary);
  oof << of.str();
  oof.close();

}

不是一个优雅的解决方案,但有效且灵活

编辑:

  • 2023 年 11 月 12 日:我在我的项目中使用类似的代码已经有一段时间了。我没有使用 std::ostringstream 作为类成员,而是将一个指针传递给该类以键入 std::ostream 并执行编写部分。它通常工作正常。
1赞 James Killian 12/3/2021 #7

我真的很喜欢韩罗的方法,并且已经验证了它的效果很好!如果将 oss 成员变量更改为使用 std::stringstream(与 ostringstream),则此类也可以用于使用重载流提取运算符进行提取,如下所示:

    template <typename T, typename std::enable_if<std::is_fundamental<T>::value, bool>::type = true>
    bostringstream& operator>> (T& v)
    {
        char buffer[sizeof(T)];
        oss.read(buffer, sizeof(T));
        v = *(T*)buffer;
        return *this;
    }

示例模板支持整数类型,如果为 std::is_compound 添加新模板,也可以支持 std::map 等类型。对于像 std::vector 这样“is_fundemental”的东西,我建议先将大小推送到流中,这样在提取端就可以拉取它以知道之后要拉取多少元素。这种方法可以很好地处理常见的 std::vector 和 std::map 类型。