2013-05-02 9 views
6

Ho una libreria di terze parti che utilizza char * (non-const) come segnaposto per i valori stringa. Qual è il modo giusto e sicuro per assegnare valori a questi tipi di dati? Ho il seguente parametro di riferimento di test che utilizza la mia classe timer per misurare i tempi di esecuzione:Qual è il modo giusto per gestire le stringhe char *?

#include "string.h" 
#include <iostream> 
#include <sj/timer_chrono.hpp> 

using namespace std; 

int main() 
{ 
    sj::timer_chrono sw; 

    int iterations = 1e7; 

    // first method gives compiler warning: 
    // conversion from string literal to 'char *' is deprecated [-Wdeprecated-writable-strings] 
    cout << "creating c-strings unsafe(?) way..." << endl; 
    sw.start(); 
    for (int i = 0; i < iterations; ++i) 
    { 
     char* str = "teststring"; 
    } 
    sw.stop(); 
    cout << sw.elapsed_ns()/(double)iterations << " ns" << endl; 

    cout << "creating c-strings safe(?) way..." << endl; 
    sw.start(); 
    for (int i = 0; i < iterations; ++i) 
    { 
     char* str = new char[strlen("teststr")]; 
     strcpy(str, "teststring"); 
    } 
    sw.stop(); 
    cout << sw.elapsed_ns()/(double)iterations << " ns" << endl; 


    return 0; 

} 

uscita:

creating c-strings unsafe(?) way... 
1.9164 ns 
creating c-strings safe(?) way... 
31.7406 ns 

Mentre il "sicuro" modo di eliminare l'avviso del compilatore rende il codice su Get 15-20 volte più lento secondo questo benchmark (1,9 nanosecondi per iterazione vs 31,7 nanosecondi per iterazione). Qual è il modo corretto e cosa sono così pericolosi in questo modo "deprecato"?

+0

Chi ha intenzione di liberare la memoria nel caso sicuro? La libreria di terze parti è mal progettata, a dire il vero. –

+2

Se stai per copiare in un buffer temporaneo, almeno usa un 'vector '. –

+1

* A parte *: 'new char [strlen (" teststr ") + 1]' per evitare di scrivere il carattere NUL all'esterno del buffer. –

risposta

10

Lo standard C++ è chiaro:

Una corda ordinaria letterale è di tipo “array di n const char” (sezione 2.14.5.8 in C++ 11).

e

L'effetto del tentativo di modificare una stringa letterale è indefinito (sezione 2.14.5.12 in C++ 11).

Per una stringa nota al momento della compilazione, il modo sicuro di ottenere un non-const char* è questo

char literal[] = "teststring"; 

si può quindi tranquillamente

char* ptr = literal; 

Se al momento della compilazione non si conosce la stringa ma conoscete la sua lunghezza potete usare un array:

char str[STR_LENGTH + 1]; 

Se non si conosce la len dopodiché dovrai utilizzare l'allocazione dinamica. Assicurati di deallocare la memoria quando le stringhe non sono più necessarie.

Funzionerà solo se l'API non assume la proprietà dello char*.

Se tenta di deallocare le stringhe internamente, dovrebbe dirlo nella documentazione e informarti sul modo corretto di allocare le stringhe. Dovrai abbinare il tuo metodo di allocazione a quello utilizzato internamente dall'API.

Il

char literal[] = "test"; 

creerà una matrice locale 5 caratteri con immagazzinaggio automatinc (cioè la variabile viene distrutta quando l'esecuzione lascia l'ambito in cui è dichiarata la variabile) e inizializzare ogni carattere nell'array con i caratteri 't', 'e', ​​'s', 't' e '\ 0'.

È possibile poi modificare questi personaggi: literal[2] = 'x';

Se si scrive questo:

char* str1 = "test"; 
char* str2 = "test"; 

poi, a seconda del compilatore, str1 e str2 può essere la stessa valore (cioè, scegliere la stessa stringa).

("Se tutti i valori letterali delle stringhe sono distinti (ovvero, sono memorizzati in oggetti non sovrapposti) è definito dall'implementazione." Nella Sezione 2.14.5.12 dello standard C++)

Può anche essere vero che sono memorizzati in una sezione di memoria di sola lettura e quindi qualsiasi tentativo di modificare la stringa provocherà un'eccezione/arresto anomalo.

sono anche, in realtà di tipo const char* in modo da questa linea:

char * str = "test";

getta effettivamente via la costanza sulla stringa, motivo per cui il compilatore emetterà l'avviso.

+0

Ottima risposta! Qual è la differenza tra {char literal [] = "teststring"; } e {char * literal = "teststring"; }? Il primo non fornisce almeno alcun avvertimento sul compilatore. Quest'ultimo assegna la stringa (array di caratteri) all'heap e quella precedente lo assegna allo stack? –

+1

@seb vedere la mia risposta aggiornata – Andrei

+0

Quindi {char literal [] = "test"; } assegna l'array di caratteri localmente (nello stack). Se faccio {char * str = literal; } dopodiché darà al puntatore un indirizzo per questo array di cariche che si trova nello stack? Se questo è vero allora ogni volta che 'letterale' esce dallo scope 'str' punta a uno spazio non definito in memoria? –

5

Il modo pericoloso è la strada da seguire per tutte le stringhe che sono conosciute in fase di compilazione.

Il tuo modo "sicuro" perde memoria ed è piuttosto orribile.

Normalmente si avrebbe un'API sana C che accetta const char *, quindi si può usare un modo sicuro corretto in C++, cioè std::string e il suo metodo c_str().

Se l'API C si assume la proprietà della stringa, il vostro "modo sicuro" ha un altro difetto: non è possibile combinare new[] e free(), passando memoria allocata utilizzando l'operatore C++ new[] ad un'API C, che si aspetta di chiamare free() su non è permesso. Se l'API C non desidera chiamare lo free() in seguito sulla stringa, dovrebbe essere opportuno utilizzare new[] sul lato C++.

Inoltre, questo è uno strano miscuglio di C++ e C.

+5

Non può davvero usare 'std :: string :: c_str()', perché sta dicendo che l'API vuole un non-const –

+2

Se l'API è scritta male, è esattamente ciò che const_cast è per. –

+0

@SebastianRedl Che potrebbe creare un UB nel suo programma –

4

Lei sembra avere un malinteso fondamentale su C stringhe qui.

cout << "creating c-strings unsafe(?) way..." << endl; 
sw.start(); 
for (int i = 0; i < iterations; ++i) 
{ 
    char* str = "teststring"; 
} 

Qui si assegna semplicemente un puntatore a una costante letterale stringa. In C e C++, i valori letterali stringa sono di tipo char[N] e puoi assegnare un puntatore a un array letterale stringa a causa dell'array "decay". (Tuttavia, è deprecato assegnare un puntatore non-const a un letterale stringa.)

Ma assegnare un puntatore a una stringa letterale non può essere ciò che si vuole fare. La tua API si aspetta una stringa non const. I valori letterali stringa sono const.

Qual è il modo giusto e sicuro per assegnare valori a questi [char * stringhe]?

Non c'è una risposta generale a questa domanda. Ogni volta che lavori con le stringhe C (o puntatori in generale), devi gestire il concetto di proprietà . C++ si prende cura di questo automaticamente con std::string. Internamente, std::string possiede un puntatore a un array char*, ma gestisce la memoria per te, quindi non devi preoccupartene. Ma quando si usano stringhe C non elaborate, è necessario mettere il pensiero nella gestione della memoria.

Il modo in cui gestisci la memoria dipende da ciò che stai facendo con il tuo programma. Se si assegna una stringa C con new[], è necessario deallocarla con delete[]. Se lo si assegna con malloc, è necessario deallocarlo con free().Una buona soluzione per lavorare con C-string in C++ è usare un puntatore intelligente che si appropria della stringa C allocata. (Ma è necessario utilizzare uno deleter che rilascia la memoria con delete[]). O puoi semplicemente usare std::vector<char>. Come sempre, non dimenticare di assegnare spazio al carattere null che termina.

Inoltre, il motivo per cui il secondo ciclo è molto più lento è perché assegna la memoria in ogni iterazione, mentre il 1 ° ciclo assegna semplicemente un puntatore a un valore letterale stringa allocato staticamente.

+0

Non so se il primo metodo non è affatto sicuro, ho solo il warning del compilatore: conversione da letterale stringa a 'char *' è deprecato [-Wdeprecated-writable-stringhe] ... C'è un motivo per preoccuparsi di questo avviso? –

+1

@seb è deprecato perché quelle stringhe sono in realtà array di 'const char' e la loro modifica porta a comportamenti non definiti. guarda la mia risposta – Andrei

Problemi correlati