2014-09-12 12 views
59

Ho avuto cose davvero strane nel mio codice. Credo di aver rintracciato verso il basso per la parte con l'etichetta "qui" (codice è semplificato, ovviamente):"" + qualcosa in C++

std::string func() { 
    char c; 
    // Do stuff that will assign to c 
    return "" + c; // Here 
} 

ogni genere di cose accadranno quando provo a cout il risultato di questa funzione. Penso di essere riuscito persino a ottenere pezzi di documentazione C++ di base, e molti uno segmentation fault. È chiaro per me che questo non funziona in C++ (ho fatto ricorso a stringstream per fare le conversioni su string ora), ma mi piacerebbe sapere perché. Dopo aver usato molto C# per un po 'e senza C++, questo mi ha causato molto dolore.

+44

Gli array si decompongono in puntatori. 'char's viene promosso a' int's. Segue l'aritmetica del puntatore. – chris

+1

Per completare il commento di chris, '" "' viene letto come 'char []', invece di 'std :: string'. – Serge

+6

Questo codice è equivalente a 'const char * str =" "; return & (str [(int) c]); '. Comportamento indefinito (a meno che C sia 0). – hyde

risposta

91
  • "" è una stringa letterale. Quelli hanno il tipo array di N const char. Questa particolare stringa letterale è una matrice di 1 const char, l'unico elemento è il terminatore nullo.

  • Gli array si decompongono facilmente in puntatori al loro primo elemento, ad es. nelle espressioni in cui è richiesto un puntatore.

  • lhs + rhs non definito per gli array come lhs e numeri interi come rhs. Ma è definito per i puntatori come lhs e interi come i rhs, con il solito aritmetico puntatore.

  • char è un tipo di dati integrato in (vale a dire, considerato come un intero da) il linguaggio di base C++.

==>stringa letterale + caratteri quindi viene interpretato come puntatore + intero.

L'espressione "" + c è più o meno equivalente a:

static char const lit[1] = {'\0'}; 
char const* p = &lit[0]; 
p + c // "" + c is roughly equivalent to this expression 

si restituisce un std::string. L'espressione "" + c restituisce un puntatore a const char. Il costruttore di std::string che si aspetta un const char* si aspetta che sia un puntatore a un array di caratteri con terminazione null.

Se c != 0, allora l'espressione "" + c porta ad un comportamento indefinito:

  • Per c > 1, il puntatore aritmetica produce un comportamento indefinito. L'aritmetica del puntatore viene definita solo sugli array e se il risultato è un elemento della stessa matrice.

  • Se char è firmato, quindi c < 0 produce comportamento non definito per lo stesso motivo.

  • Per c == 1, il puntatore aritmetico non produce comportamento non definito.Questo è un caso speciale; il puntamento a un elemento dopo l'ultimo elemento di un array è consentito (non è consentito utilizzare ciò che indica, però). Porta ancora a comportamento indefinito dal momento che il costruttore std::string chiamato qui richiede che il suo argomento sia un puntatore a un array valido (e una stringa terminata da null). L'elemento one-last-the-last non fa parte dell'array stesso. La violazione di questo requisito porta anche a UB.


Ciò che probabilmente ora che succede è che il costruttore di std::string cerca di determinare la dimensione della stringa null-terminated si passò, cercando il (primo) carattere nella matrice che è uguale '\0' :

string(char const* p) 
{ 
    // simplified 
    char const* end = p; 
    while(*end != '\0') ++end; 
    //... 
} 

questo produrrà una violazione di accesso, o la stringa che crea contiene "garbage". È anche possibile che il compilatore presuma che questo comportamento non definito non accadrà mai, e fa alcune ottimizzazioni divertenti che si traducono in un comportamento strano.


A proposito, clang++3.5 emits a nice warning per questo frammento:

avvertimento: l'aggiunta di 'char' ad una stringa non aggiunge alla stringa [-Wstring-plus-int]

return "" + c; // Here 
     ~~~^~~ 

nota: utilizzare matrice indicizzazione per silenziare questo avviso

+2

Quindi essenzialmente un mucchio di conversioni implicite allora. Ok, grazie per l'input! – wlyles

+1

'" "+ c' è solo un comportamento indefinito per' c> 1', perché '" "' ha una dimensione di 1 e lo standard consente esplicitamente ai puntatori un elemento oltre la fine della memoria allocata per rendere possibile l'iterazione. Altrimenti, anche un semplice ciclo su tutta la lunghezza di un array avrebbe UB. Tuttavia, _dereferencing_ quel puntatore è UB, che è ciò che accade nel costruttore di 'std :: string'. –

+2

@SimonLehmann Sì, l'UB nel caso 'c == 1' deriva dal chiamare il ctor' std :: string' con qualcosa che non è un puntatore a una stringa terminata da null (che implica un puntatore a un array valido) . Cioè, per 'c> 1' c'è un'altra fonte di UB che si applica prima della chiamata di ctor, ma in ogni caso c'è UB per' c> 0'. – dyp

9

Questa dichiarazione di reso

return "" + c; 

è valido. C'è usato il cosiddetto aritmetico del puntatore. Il letterale stringa "" viene convertito in puntatore al suo primo carattere (in questo caso allo zero finale) e il valore intero memorizzato in c viene aggiunto al puntatore. Così il risultato di espressione

"" + c 

è di tipo const char *

classe std :: string ha costruttore di conversione che accetta argomento di tipo const char *. Il problema è che questo puntatore può puntare oltre la stringa letterale. Quindi la funzione ha un comportamento indefinito.

Non vedo alcun senso nell'uso di questa espressione. Se si vuole costruire una stringa in base a un carattere si potrebbe scrivere per esempio

return std::string(1, c); 

la differenza tra C++ e C# è che in C letterali # stringhe hanno tipo System.String che ha sovraccaricato operatore + per archi e caratteri (che sono caratteri unicode in C#). In C++ le stringhe letterali sono array di caratteri costanti e la semantica di operator + per matrici e interi è diversa. Le matrici vengono convertite in puntatori ai loro primi elementi e viene utilizzata l'aritmetica del puntatore.

È la classe standard std :: string che ha l'operatore + sovraccarico per i caratteri. I valori letterali stringa in C++ non sono oggetti di questa classe che è di tipo std :: string.

+0

"Questa dichiarazione di ritorno' return "" + c; 'è valida." - Non se c> 1. E se c == 1, il valore di ritorno non è * utilizzabile * se non quello di sottrarre 1 da esso. –

26

Ci sono molte spiegazioni su come il compilatore interpreta questo codice, ma quello che probabilmente volevi sapere è ciò che hai sbagliato.

Sembra che ti aspetti il ​​comportamento + da std::string. Il problema è che nessuno degli operandi in realtà è un std::string. C++ analizza i tipi di operandi, non il tipo finale dell'espressione (qui il tipo restituito, std::string) per risolvere l'overload. Non sceglierà la versione std::string di + se non vede uno std::string.

Se si dispone di un comportamento speciale per un operatore (o lo ha scritto, o ottenuto una libreria che lo fornisce), tale comportamento si applica solo quando almeno uno degli operandi ha un tipo di classe (o un riferimento al tipo di classe e all'utente anche le enumerazioni definite contano).

Se hai scritto

std::string("") + c 

o

std::string() + c 

o

""s + c // requires C++14 

allora si otterrebbe il std::string comportamento dell'operatore +.

(Si noti che nessuno di questi sono in realtà buone soluzioni, perché tutti fanno di breve durata std::string istanze che possono essere evitati con std::string(1, c))

La stessa cosa vale per le funzioni. Ecco un esempio:

std::complex<double> ipi = std::log(-1.0); 

Otterrai un errore di runtime, invece del numero immaginario previsto. Questo perché il compilatore non ha idea che dovrebbe usare il logaritmo complesso qui. Il sovraccarico sembra solo agli argomenti e l'argomento è un numero reale (tipo double, in realtà).

I sovraccarichi dell'operatore SONO funzioni e obbediscono alle stesse regole.

+0

@dyp: Grazie, corretto. Probabilmente stavo pensando alle regole per il sovraccarico di funzioni e operatori in 'std' ... dove un tipo standard può soddisfare il requisito di" classe o riferimento alla classe "e un puntatore al tipo personalizzato può soddisfare il requisito relativo ai tipi personalizzati. –

+0

o regole ADL, che includono anche i puntatori. Non sei sicuro di quali regole ti riferisci, le funzioni di overloading nello spazio dei nomi 'std' sono proibite a meno che non sia esplicitamente permesso. – dyp

+0

@dyp: Immagino che sia 17.6.4.2.1/1, che non è molto specifico ... solo "se la dichiarazione dipende da un tipo definito dall'utente" –

Problemi correlati