2009-10-13 12 views
50

Di solito uso stringstream per scrivere nella stringa in memoria. C'è un modo per scrivere su un buffer di char in modalità binaria? Si consideri il seguente codice:Ci sono flussi di memoria binari in C++

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

La memoria a ch sarà simile a questa: 0x313233 - i codici ASCII dei caratteri 1, 2 e 3. Sto cercando un modo per scrivere i valori binari stessi. Cioè, voglio 0x010203 nella memoria. Il problema è che voglio essere in grado di scrivere una funzione

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

E decidere al di fuori che tipo di flusso da utilizzare. Qualcosa del genere:

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

Qualche idea?

+1

Questo è esadecimale, non binario. Perché non puoi scrivere 0x01, 0x02, ecc., Anche se ... quelli sono veri caratteri ASCII, dopo tutto. – jrockway

+1

Vuole che il contenuto della memoria (i byte effettivi) sia 0x010203 (66051 decimale), non la stringa "0x010203". – KeithB

+1

Ho modificato la domanda. Spero che ora sia più chiaro. – FireAphis

risposta

5

Bene, basta usare caratteri, non interi.

s << char(1) << char(2) << char(3); 
30

di leggere e scrivere dati binari nei flussi, tra stringstreams, utilizzare read() e write) funzioni membro (. So

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

Questo dà 0x4030201 sul mio sistema.

Edit: di fare questo lavoro in modo trasparente con gli operatori di inserimento ed estrazione (< < e >>), la soluzione migliore per creare uno streambuf derivata che fa la cosa giusta, e passa che a tutto ciò che i flussi che si desidera uso.

+0

Risponde sicuramente alla prima parte della domanda, ma c'è un modo per rendere l'inserimento sempre uguale (ad esempio << a) ma la rappresentazione dei dati interni differisce a seconda del tipo di flusso? – FireAphis

+0

Il tuo streambuf non può farlo; la formattazione viene eseguita nei metodi istream (non virtuali) e ostream e il risultato è ciò che vede streambuf. –

+0

La domanda mostra in realtà il risultato in memoria '0x010203' mentre questo probabilmente produrrà' 0x00000001 0x00000002 0x00000003' (assumendo 'sizeof (int) == 4'). – MSalters

1

sovraccaricare alcuni operatori insoliti funziona piuttosto bene. Qui di seguito ho scelto sovraccaricare < = perché ha la stessa associatività da sinistra a destra come < < e ha in qualche modo un primo look-and-feel ...

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

ci sono diversi svantaggi :

  • il nuovo significato di < = possono confondere i lettori o portare a risultati inaspettati:

    cout <= 31 <= 32; 
    

    non darà lo stesso risultato

    cout <= (31 <= 32); 
    
  • l'endianess non è chiaramente menzionate alla lettura del codice, come illustrato nell'esempio precedente.

  • non può mescolare semplicemente con < < perché non appartiene al stesso gruppo di precedenza.Io di solito uso parentesi per chiarire quali come:

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

Questa è una bella dimostrazione di concetto, ma si noti che gli operatori sovraccaricati di C++ sono considerati malvagi perché consentono * questo *. Il sovraccarico non ovvio di "<<" è giustificato solo perché è un sovraccarico ** standard **. Non dovrebbero essere inventati nuovi overload di hacker e il sovraccarico stesso dovrebbe essere usato con molta attenzione. – cubuspl42

0

Per questo caso d'uso io stesso ho implementato un "operatore di spostamento prima":

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

metterlo da qualche parte conveniente e usare in questo modo :

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

Vantaggi:

  • chainable
  • automatico sizeof()
  • prende array e istanze di struct/classe, troppo

Svantaggi:

  • pericoloso per i non-POD oggetti: perdite puntatori e imbottitura
  • l'output è specifico della piattaforma: padding, endianess, tipi interi
0

Si può fare questo genere di cose con i modelli. E.g:

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

Potrebbe essere utile un po 'di pulizia, ma è funzionale. E.g:

//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); 
Problemi correlati