Widzę dwa 'some'
literały w kodzie asemblera wygenerowanym przez MSVC, ale tylko jeden z clang i gcc. Prowadzi to do zupełnie innych wyników wykonania kodu.
static const char *A = "some";
static const char *B = "some";
void f() {
if (A == B) {
throw "Hello, string merging!";
}
}
Czy ktoś może wyjaśnić różnicę i podobieństwa między tymi wynikami kompilacji? Dlaczego clang / gcc optymalizuje coś, nawet jeśli żadna optymalizacja nie jest wymagana? Czy to jakieś niezdefiniowane zachowanie?
Zauważyłem również, że jeśli zmienię deklaracje na te pokazane poniżej, clang / gcc / msvc w ogóle nie pozostawi żadnych "some"
w kodzie asemblera. Dlaczego zachowanie jest inne?
static const char A[] = "some";
static const char B[] = "some";
c++
language-lawyer
string-literals
string-interning
Eugene Kosov
źródło
źródło
Odpowiedzi:
To nie jest niezdefiniowane zachowanie, ale nieokreślone zachowanie. Dla napisowych ,
Oznacza to, że wynik
A == B
może byćtrue
lubfalse
, na którym nie powinieneś polegać.Ze standardu [lex.string] / 16 :
źródło
Inne odpowiedzi wyjaśniały, dlaczego nie można oczekiwać, że adresy wskaźników będą różne. Jednak możesz łatwo przepisać to w sposób, który to gwarantuje
A
iB
nie porównuje równych:static const char A[] = "same"; static const char B[] = "same";// but different void f() { if (A == B) { throw "Hello, string merging!"; } }
Różnica polega na tym
A
iB
są teraz tablice znaków. Oznacza to, że nie są one wskaźnikami, a ich adresy muszą być różne, tak jak musiałyby być adresy dwóch zmiennych całkowitych. C ++ myli to, ponieważ sprawia, że wskaźniki i tablice wydają się zamienne (operator*
ioperator[]
zachowują się tak samo), ale są naprawdę różne. Np. Coś takiegoconst char *A = "foo"; A++;
jest całkowicie legalne, aleconst char A[] = "bar"; A++;
nie jest.Jednym ze sposobów myślenia o tej różnicy jest
char A[] = "..."
stwierdzenie: „daj mi blok pamięci i wypełnij go znakami,...
po których następuje\0
”, natomiastchar *A= "..."
mówi „podaj adres, pod którym mogę znaleźć znaki,...
po których następuje\0
”.źródło
*p
ip[0]
nie tylko „wydają się zachowywać tak samo”, ale z definicji są identyczne (pod warunkiem, żep+0 == p
jest to relacja tożsamości, ponieważ0
jest to neutralny element w dodawaniu wskaźnika do liczby całkowitej). W końcup[i]
jest definiowany jako*(p+i)
. Odpowiedź jest jednak słuszna.typeof(*p)
itypeof(p[0])
są jednymi i drugimi,char
więc naprawdę niewiele pozostało, co mogłoby być inne. Zgadzam się, że „wydaje się zachowywać tak samo” nie jest najlepszym sformułowaniem, ponieważ semantyka jest tak różna. Twój post przypomniał mi najlepszy sposób dostępu elementów macierzy C ++:0[p]
,1[p]
,2[p]
itd. To jest jak robią to profesjonaliści, przynajmniej jeśli chcą zmylić ludzi, którzy urodzili się po języku programowania C.To, czy kompilator zdecyduje się użyć tej samej lokalizacji ciągu dla implementacji
A
iB
zależy od tego, czy. Formalnie możesz powiedzieć, że zachowanie twojego kodu jest nieokreślone .Obie opcje poprawnie implementują standard C ++.
źródło
Jest to optymalizacja w celu zaoszczędzenia miejsca, często nazywana „łączeniem ciągów”. Oto dokumentacja dla MSVC:
https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx
Dlatego jeśli dodasz / GF do wiersza poleceń, powinieneś zobaczyć to samo zachowanie z MSVC.
Nawiasem mówiąc, prawdopodobnie nie powinieneś porównywać ciągów za pomocą takich wskaźników, każde przyzwoite narzędzie do analizy statycznej oznaczy ten kod jako wadliwy. Musisz porównać to, na co wskazują, a nie rzeczywiste wartości wskaźnika.
źródło