Warum verwenden (nur) einige Compiler dieselbe Adresse für identische String-Literale?

91

https://godbolt.org/z/cyBiWY

Ich kann zwei 'some'Literale im Assembler-Code sehen, der von MSVC generiert wurde, aber nur eines mit clang und gcc. Dies führt zu völlig unterschiedlichen Ergebnissen der Codeausführung.

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

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

Kann jemand den Unterschied und die Ähnlichkeiten zwischen diesen Kompilierungsausgaben erklären? Warum optimiert clang / gcc etwas, auch wenn keine Optimierungen angefordert werden? Ist das eine Art undefiniertes Verhalten?

Ich stelle auch fest, dass clang / gcc / msvc, wenn ich die Deklarationen in die unten gezeigten ändere, überhaupt keine "some"im Assembler-Code belässt . Warum ist das Verhalten anders?

static const char A[] = "some";
static const char B[] = "some";
Eugene Kosov
quelle
4
stackoverflow.com/a/52424271/1133179 Eine nette relevante Antwort auf eine eng verwandte Frage mit Standardzitaten.
luk32
6
Bei MSVC steuert die Compileroption / GF dieses Verhalten. Siehe docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd
1
Zu Ihrer Information, dies kann auch für Funktionen passieren.
user541686
Wie
verschmelzen

Antworten:

108

Dies ist kein undefiniertes Verhalten, sondern ein nicht spezifiziertes Verhalten. Für Stringliterale ,

Der Compiler darf Speicher für gleiche oder überlappende String-Literale kombinieren, muss dies jedoch nicht. Das bedeutet, dass identische Zeichenfolgenliterale beim Vergleich mit dem Zeiger gleich sein können oder nicht.

Das heißt, das Ergebnis von A == Bkönnte sein trueoder false, von dem Sie sich nicht verlassen sollten.

Aus dem Standard [lex.string] / 16 :

Ob alle Zeichenfolgenliterale unterschiedlich sind (dh in nicht überlappenden Objekten gespeichert sind) und ob aufeinanderfolgende Auswertungen eines Zeichenfolgenliterals dasselbe oder ein anderes Objekt ergeben, ist nicht angegeben.

songyuanyao
quelle
36

Die anderen Antworten erklärten, warum Sie nicht erwarten können, dass die Zeigeradressen unterschiedlich sind. Sie können dies jedoch leicht so umschreiben, dass dies garantiert Aund Bnicht gleich ist:

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

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

Der Unterschied ist , daß Aund Bsind nun Arrays von Zeichen. Dies bedeutet, dass sie keine Zeiger sind und ihre Adressen genau so unterschiedlich sein müssen wie die von zwei ganzzahligen Variablen. C ++ verwirrt dies, weil es Zeiger und Arrays austauschbar erscheinen lässt ( operator*und operator[]sich gleich zu verhalten scheint), aber sie sind wirklich unterschiedlich. ZB ist so etwas const char *A = "foo"; A++;vollkommen legal, ist es aber const char A[] = "bar"; A++;nicht.

Eine Möglichkeit, über den Unterschied nachzudenken, besteht darin, char A[] = "..."dass "Geben Sie mir einen Speicherblock und füllen Sie ihn mit den Zeichen, ...gefolgt von \0", während char *A= "...""Geben Sie mir eine Adresse, an der ich die Zeichen finden kann, denen ...gefolgt wird \0".

tobi_s
quelle
8
Dies wäre eine noch bessere Antwort, wenn Sie erklären könnten, warum es anders ist.
Mark Ransom
Man beachte , dass *pund p[0]nicht nur „scheint dasselbe zu verhalten“ , aber per Definition ist identisch (vorausgesetzt , dass p+0 == peine Identitätsbeziehung ist , weil 0das neutrale Element in Zeiger-Ganzzahl - Addition). Immerhin p[i]ist definiert als *(p+i). Die Antwort macht jedoch einen guten Punkt.
Peter - Monica
typeof(*p)und typeof(p[0])sind beide charso, dass es wirklich nicht mehr viel gibt, was anders sein könnte. Ich stimme zu, dass "sich anscheinend gleich zu verhalten scheint" nicht der beste Wortlaut ist, weil die Semantik so unterschiedlich ist. Ihr Beitrag erinnerte mich an dem besten Weg , um Zugang Elemente von C ++ Arrays: 0[p], 1[p], 2[p]etc. Dies ist , wie die Profis tun es, zumindest wenn sie wollen die Menschen verwirren , die nach der Programmiersprache C geboren wurden.
tobi_s
Das ist interessant, und ich war versucht, einen Link zu den C-FAQ hinzuzufügen, aber mir wurde klar, dass es viele verwandte Fragen gibt, aber keine scheint hier auf den Punkt dieser Frage zu kommen.
tobi_s
23

Ob ein Compiler denselben String-Speicherort für Aund verwendet, Bhängt von der Implementierung ab. Formal können Sie sagen, dass das Verhalten Ihres Codes nicht spezifiziert ist .

Beide Optionen implementieren den C ++ - Standard korrekt.

Bathseba
quelle
Das Verhalten des Codes besteht darin, entweder eine Ausnahme auszulösen oder nichts zu tun , was vor der ersten Ausführung des Codes in nicht spezifizierter Weise ausgewählt wurde . Dies bedeutet nicht, dass das Verhalten als Ganzes nicht spezifiziert ist - lediglich, dass der Compiler jedes Verhalten auf eine Weise auswählen kann, die er für richtig hält, bevor das Verhalten zum ersten Mal beobachtet wird.
Supercat
3

Es ist eine Optimierung, um Platz zu sparen, die oft als "String-Pooling" bezeichnet wird. Hier sind die Dokumente für MSVC:

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

Wenn Sie der Befehlszeile / GF hinzufügen, sollte das gleiche Verhalten bei MSVC auftreten.

Übrigens sollten Sie Zeichenfolgen wahrscheinlich nicht über solche Zeiger vergleichen. Jedes anständige statische Analysetool kennzeichnet diesen Code als fehlerhaft. Sie müssen vergleichen, auf was sie zeigen, nicht die tatsächlichen Zeigerwerte.

Paulm
quelle