web-dev-qa-db-fra.com

Existe-t-il des flux de mémoire binaire en C ++

J'utilise généralement stringstream pour écrire dans une chaîne en mémoire. Existe-t-il un moyen d'écrire dans un tampon de caractères en mode binaire? Considérez le code suivant:

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

La mémoire à ch ressemblera à ceci: 0x313233 - les codes ASCII des caractères 1, 2 et 3. Je cherche un moyen d'écrire les valeurs binaires elles-mêmes C'est-à-dire que je veux 0x010203 dans la mémoire. Le problème est que je veux pouvoir écrire une fonction

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

Et décidez en dehors du type de flux à utiliser. Quelque chose comme ça:

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

Des idées?

52
FireAphis

Pour lire et écrire des données binaires dans des flux, y compris des flux de chaînes, utilisez les fonctions membres read () et write (). Donc

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";

Cela donne 0x4030201 sur mon système.

Edit: Pour que cela fonctionne de manière transparente avec les opérateurs d'insertion et d'extraction (<< et >>), il est préférable de créer un streambuf dérivé qui fait la bonne chose, et de le transmettre aux flux que vous souhaitez utiliser.

34
KeithB

Vous pouvez faire ce genre de choses avec des modèles. Par exemple:

//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));
}

Il pourrait utiliser un nettoyage, mais il est fonctionnel. Par exemple:

//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);
4
Samuel Powell

Eh bien, utilisez simplement des caractères, pas des entiers.

s << char(1) << char(2) << char(3);
4
Lukáš Lalinský

surcharger certains opérateurs inhabituels fonctionne plutôt bien. Ici ci-dessous, j'ai choisi de surcharger <= parce qu'il a la même associativité de gauche à droite que << et a en quelque sorte une apparence proche ...

#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;
}

Il y a plusieurs inconvénients:

  • la nouvelle signification de <= peut dérouter les lecteurs ou conduire à des résultats inattendus:

    cout <= 31 <= 32;
    

    ne donnera pas le même résultat que

    cout <= (31 <= 32);
    
  • l'endianess n'est pas clairement mentionnée à la lecture du code, comme illustré dans l'exemple ci-dessus.

  • il ne peut pas se mélanger simplement avec << car il n'appartient pas au même groupe de priorité. J'utilise habituellement des parenthèses pour clarifier, par exemple:

    ( cout <= htonl(a) <= htonl(b) ) << endl;
    
3
Jean Daniel Pauget

Pour ce cas d'utilisation, je me suis implémenté un "opérateur de décalage brut":

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;
}

Mettez-le dans un endroit pratique et utilisez-le comme ceci:

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

Avantages:

  • chaînable
  • automatique sizeof()
  • prend aussi des tableaux et des instances struct/class

Désavantages:

  • dangereux pour les objets non POD: pointeurs de fuite et rembourrage
  • la sortie est spécifique à la plateforme: padding, endianess, integer types
2
kamikaze