Perché (solo) alcuni compilatori usano lo stesso indirizzo per stringhe letterali identiche?


92

https://godbolt.org/z/cyBiWY

Riesco a vedere due 'some'letterali nel codice assembler generato da MSVC, ma solo uno con clang e gcc. Ciò porta a risultati completamente diversi dell'esecuzione del codice.

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

Qualcuno può spiegare la differenza e le somiglianze tra questi output della compilation? Perché clang / gcc ottimizza qualcosa anche quando non sono richieste ottimizzazioni? È una specie di comportamento indefinito?

Noto anche che se cambio le dichiarazioni in quelle mostrate di seguito, clang / gcc / msvc non ne lascia affatto "some"nel codice assembler. Perché il comportamento è diverso?

static const char A[] = "some";
static const char B[] = "some";

4
stackoverflow.com/a/52424271/1133179 Qualche bella risposta pertinente a una domanda strettamente correlata, con virgolette standard.
luk32


6
Per MSVC, l'opzione del compilatore / GF controlla questo comportamento. Vedere docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd

1
Cordiali saluti, questo può accadere anche per le funzioni.
user541686

Risposte:


109

Questo non è un comportamento indefinito, ma un comportamento non specificato. Per i letterali stringa ,

Al compilatore è consentito, ma non obbligatorio, combinare l'archiviazione per stringhe letterali uguali o sovrapposte. Ciò significa che i valori letterali di stringa identici possono o meno confrontare uguali se confrontati tramite puntatore.

Ciò significa che il risultato di A == Bpotrebbe essere trueofalse , da cui non dovresti dipendere.

Dallo standard, [lex.string] / 16 :

Se tutti i valori letterali stringa sono distinti (ovvero, vengono memorizzati in oggetti non sovrapposti) e se le valutazioni successive di un valore letterale stringa restituiscono lo stesso oggetto o un oggetto diverso non è specificato.


36

Le altre risposte hanno spiegato perché non ci si può aspettare che gli indirizzi del puntatore siano diversi. Eppure puoi facilmente riscriverlo in un modo che lo garantisca Ae Bnon paragonarlo allo stesso modo:

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

La differenza è che A e Bora sono matrici di caratteri. Ciò significa che non sono puntatori e i loro indirizzi devono essere distinti proprio come dovrebbero essere quelli di due variabili intere. C ++ confonde questo perché fa sembrare i puntatori e gli array intercambiabili ( operator*e operator[]sembrano comportarsi allo stesso modo), ma sono davvero diversi. Ad esempio qualcosa di simile const char *A = "foo"; A++;è perfettamente legale, maconst char A[] = "bar"; A++; non lo è.

Un modo per pensare alla differenza è che char A[] = "..."dice "dammi un blocco di memoria e riempilo con i caratteri ...seguiti da \0", mentre char *A= "..."dice "dammi un indirizzo in cui posso trovare i caratteri ...seguiti da\0 ".


8
Questa sarebbe una risposta ancora migliore se potessi spiegare perché è diverso.
Mark Ransom

Si noti che *pe p[0]non solo "sembrano comportarsi allo stesso modo" ma per definizione sono identici (ammesso che p+0 == psia una relazione di identità perché 0è l'elemento neutro nell'addizione puntatore-intero). Dopotutto, p[i]è definito come *(p+i). La risposta è comunque un buon punto.
Peter - Ripristina Monica il

typeof(*p)e typeof(p[0])sono entrambi, charquindi non è rimasto molto che potrebbe essere diverso. Sono d'accordo che "sembra che si comportino allo stesso modo" non è la migliore formulazione, perché la semantica è molto diversa. Il tuo post mi ha ricordato il modo migliore per gli elementi di accesso di matrici C ++: 0[p], 1[p], 2[p]ecc Questo è come i pro lo fanno, almeno quando vogliono confondere le persone che sono nati dopo il linguaggio di programmazione C.
tobi_s


Questo è interessante, e sono stato tentato di aggiungere un collegamento alle FAQ di C, ma mi sono reso conto che ci sono molte domande correlate, ma nessuna sembra andare proprio al punto di questa domanda qui.
tobi_s

23

Se un compilatore sceglie o meno di utilizzare la stessa posizione della stringa Ae Bdipende dall'implementazione. Formalmente puoi dire che il comportamento del tuo codice non è specificato .

Entrambe le scelte implementano correttamente lo standard C ++.


Il comportamento del codice consiste nel lanciare un'eccezione o non fare nulla, scelto prima della prima esecuzione del codice, in modo non specificato . Ciò non significa che il comportamento nel suo insieme non sia specificato, ma semplicemente che il compilatore può selezionare uno dei comportamenti in qualsiasi modo ritenga opportuno prima della prima volta che il comportamento viene osservato.
supercat

3

È un'ottimizzazione per risparmiare spazio, spesso chiamata "string pooling". Ecco i documenti per MSVC:

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

Pertanto, se aggiungi / GF alla riga di comando dovresti vedere lo stesso comportamento con MSVC.

A proposito, probabilmente non dovresti confrontare le stringhe tramite puntatori del genere, qualsiasi strumento di analisi statica decente contrassegnerà quel codice come difettoso. È necessario confrontare ciò a cui puntano, non i valori effettivi del puntatore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.