2016-04-28 21 views
10

sto usando sprintf funzione in C++ 11, nel seguente modo:Usando sprintf con std :: string in C++

std::string toString() 
{ 
    std::string output; 
    uint32_t strSize=512; 
    do 
    { 
     output.reserve(strSize); 
     int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u", 
      INDEX_RECORD_TYPE_SERIALIZATION_HEADER, 
      FORAMT_VERSION, 
      contentType, 
      contentFormatVersion, 
      magic, 
      segmentId); 

     strSize *= 2; 
    } while (ret < 0); 

    return output; 
} 

C'è un modo migliore per fare questo, oltre a controllare ogni volta se l'riservata lo spazio era abbastanza? Per possibilità future di aggiungere più cose.

+1

Stai utilizzando 'Snprintf'? Perché 'sprintf', come mostrato nel codice, non ha modo di determinare la dimensione del buffer. 'snprintf' restituirebbe anche la dimensione del buffer richiesta, quindi potreste semplicemente usare il valore restituito +1 come nuovo' strSize'. –

+1

Questo codice è molto sbagliato. 'reserve' non cambia la dimensione della stringa, e' sprintf' non restituisce il negativo solo perché hai scritto fuori limite. Devi allocare lo spazio che ti serve * prima * per scrivere fuori limite. –

+0

Domanda correlata: http://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf –

risposta

13

tuo costrutto - scrittura nel buffer di informazioni ricevuta da c_str() - è comportamento indefinito, anche se è stata selezionata la capacità della stringa di anticipo. (Il valore restituito è un puntatore a const char, e la funzione stessa segnato const, per un motivo.)

Non mescolare C e C++, soprattutto non per la scrittura in rappresentazione dell'oggetto interno. (Si tratta di rompere OOP molto di base.) Utilizzare C++, per sicurezza di tipo e non in esecuzione in errore di conversione specifier/parametro, se non altro.

std::ostringstream s; 
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORMAT_VERSION 
    // ...and so on... 
    ; 
std::string output = s.str(); 

Alternativa:

std::string output = "Type=" + std::to_string(INDEX_RECORD_TYPE_SERIALIZATION_HEADER) 
        + " Version=" + std::to_string(FORMAT_VERSION) 
        // ...and so on... 
        ; 
+0

come posso simulare la formattazione della stringa in ostringstream, come magic =% 04x e così via? –

+1

@danieltheman: ['setw'] (http://en.cppreference.com/w/cpp/io/manip/setw), [' setfill'] (http://en.cppreference.com/w/cpp/IO/manip/setfill). – DevSolar

+0

Il tuo commento * 'Il valore restituito è un puntatore a ** const ** char, e la funzione stessa marcata ** const **, per una ragione' * implica che il codice presentato * non dovrebbe nemmeno compilare * a causa di 'const 'Qualificatore non corrispondente a 'sprintf (output.c_str(), ...' espressione. Quindi è falso OP * sta usando *' sprintf' nel modo in cui ha descritto, perché un estratto presentato non è un codice eseguibile ... – CiaPan

1

Il codice è sbagliato. reserve alloca la memoria per la stringa, ma non ne modifica la dimensione. La scrittura nel buffer restituito da c_str non modifica neanche le sue dimensioni. Quindi la stringa crede ancora che la sua dimensione sia 0, e tu hai appena scritto qualcosa nello spazio inutilizzato nel buffer della stringa. (Probabilmente, tecnicamente, il codice ha un comportamento non definito, perché la scrittura in c_str non è definita, quindi potrebbe accadere di tutto).

Che cosa si vuole veramente fare è dimenticare sprintf e simili funzioni C-style, e utilizzare il modo in cui C++ di formattazione stringa flussi — stringa:

std::ostringstream ss; 
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER 
    << " Version=" << FORAMT_VERSION 
    << /* ... the rest ... */; 
return ss.str(); 
0

Sì, c'è!

In C, il modo migliore è quello di associare un file con il dispositivo nulla e fare un manichino printf del output desiderato ad esso, per imparare quanto spazio ci vorrebbe se in realtà stampata. Quindi allocare il buffer appropriato e sprintf gli stessi dati ad esso.

In C++ è possibile associare il flusso di output anche a un dispositivo null e verificare il numero di caratteri stampati con std::ostream::tellp. Tuttavia, l'utilizzo di ostringstream rappresenta una soluzione molto più efficace: vedere le risposte di DevSolar o Angew.

+0

Manca completamente il punto in cui non è possibile scrivere sul buffer restituito da 'c_str()', e inutilmente complicato anche da allora dato che c'è 'snprintf()'/'snprintf_s'. – DevSolar

+0

'snprintf' è migliore della modifica del dispositivo nullo. Ma devi usare quel trucco per 'swprintf'. –

6

I modelli C++ mostrati in altre risposte sono più belli, ma per completezza, ecco un modo corretto con sprintf:

auto format = "your %x format %d string %s"; 
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/); 
std::string output(size + 1, '\0'); 
std::sprintf(&output[0], format, /* Arguments go here*/); 

Prestare attenzione alla

  • Devi resize la stringa. reserve non modifica la dimensione del buffer. Nel mio esempio, costruisco direttamente una stringa di dimensioni corrette.
  • c_str() restituisce un const char*. Non è possibile passare a sprintf.
  • std::string buffer non era garantito contiguo prima di C++ 11 e questo si basa su tale garanzia. Se è necessario supportare piattaforme conformi a pre-C++ 11 esatte che utilizzano l'implementazione della corda per std::string, allora è preferibile lo sprint nello std::vector<char> e quindi copiare il vettore nella stringa.
  • Questo funziona solo se gli argomenti non vengono modificati tra il calcolo della dimensione e la formattazione; utilizzare le copie locali delle variabili o le primitive di sincronizzazione dei thread per il codice multi-thread.
+0

Interessante; Non sono abbastanza sicuro se il costrutto (scrivendo a '& output [0]') sia aderente alla lettera dello standard, comunque. Questo probabilmente funzionerà per quasi tutte le implementazioni di 'std :: string', ma per quanto riguarda la definizione, ho dei dubbi. – DevSolar

+0

@DevSolar Se non ricordo male, la contiguità del buffer 'std :: string' è garantita da C++ 11. Prima di ciò, non era garantito.Per quanto ne so, non ci sono altri motivi per cui questo non funzioni. – user2079303

+0

@ user2079303 costruttore di stringhe che riceve un numero in esso? non penso che ci sia qualcosa di simile in C++, puoi elaborare per favore? sto parlando di questo: std :: string output (size + 1); –

1

Possiamo mescolare il codice da qui https://stackoverflow.com/a/36909699/2667451 e qui https://stackoverflow.com/a/7257307 e risultato essere così:

template <typename ...Args> 
std::string stringWithFormat(const std::string& format, Args && ...args) 
{ 
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...); 
    std::string output(size + 1, '\0'); 
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...); 
    return output; 
} 
Problemi correlati