Mengapa (hanya) beberapa kompiler menggunakan alamat yang sama untuk string literal yang identik?

92

https://godbolt.org/z/cyBiWY

Saya dapat melihat dua 'some'literal dalam kode assembler yang dihasilkan oleh MSVC, tetapi hanya satu dengan clang dan gcc. Ini mengarah pada hasil eksekusi kode yang sangat berbeda.

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

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

Adakah yang bisa menjelaskan perbedaan dan persamaan antara hasil kompilasi tersebut? Mengapa clang / gcc mengoptimalkan sesuatu meskipun tidak ada pengoptimalan yang diminta? Apakah ini semacam perilaku yang tidak terdefinisi?

Saya juga memperhatikan bahwa jika saya mengubah deklarasi seperti yang ditunjukkan di bawah ini, clang / gcc / msvc tidak meninggalkan "some"kode assembler sama sekali. Mengapa perilakunya berbeda?

static const char A[] = "some";
static const char B[] = "some";
Eugene Kosov
sumber
4
stackoverflow.com/a/52424271/1133179 Beberapa jawaban bagus yang relevan untuk pertanyaan yang terkait erat, dengan tanda kutip standar.
Lukas32
6
Untuk MSVC, opsi compiler / GF mengontrol perilaku ini. Lihat docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd
1
FYI, ini juga bisa terjadi untuk fungsi.
pengguna541686

Jawaban:

109

Ini bukanlah perilaku yang tidak ditentukan, tetapi perilaku yang tidak ditentukan. Untuk literal string ,

Kompilator diperbolehkan, tetapi tidak diharuskan, untuk menggabungkan penyimpanan untuk string literal yang sama atau tumpang tindih. Itu berarti bahwa string literal identik mungkin atau mungkin tidak dibandingkan jika dibandingkan dengan pointer.

Itu berarti hasil dari A == Bmungkin trueataufalse , yang seharusnya tidak Anda andalkan.

Dari standar, [lex.string] / 16 :

Apakah semua string literal berbeda (yaitu, disimpan dalam objek yang tidak tumpang tindih) dan apakah evaluasi berturut-turut dari string-literal menghasilkan objek yang sama atau berbeda tidak ditentukan.

songyuanyao
sumber
36

Jawaban lain menjelaskan mengapa Anda tidak dapat mengharapkan alamat penunjuk berbeda. Namun Anda dapat dengan mudah menulis ulang ini dengan cara yang menjamin itu Adan Btidak sebanding:

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

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

Perbedaannya adalah itu Adan Bsekarang adalah array karakter. Ini berarti bahwa mereka bukan pointer dan alamat mereka harus berbeda seperti dua variabel integer yang seharusnya. C ++ membingungkan ini karena membuat pointer dan array tampak dapat dipertukarkan ( operator*dan operator[]tampaknya berperilaku sama), tetapi keduanya sangat berbeda. Misalnya sesuatu seperti const char *A = "foo"; A++;itu legal, tetapi const char A[] = "bar"; A++;tidak.

Salah satu cara untuk memikirkan perbedaannya adalah dengan char A[] = "..."mengatakan "beri saya blok memori dan isi dengan karakter ...diikuti oleh \0", sedangkan char *A= "..."mengatakan "beri saya alamat di mana saya dapat menemukan karakter ...diikuti oleh \0".

tobi_s
sumber
8
Ini akan menjadi jawaban yang lebih baik jika Anda bisa menjelaskan mengapa itu berbeda.
Mark Ransom
Perhatikan bahwa *pdan p[0]tidak hanya "tampaknya berperilaku sama" tetapi menurut definisi adalah identik (asalkan itu p+0 == padalah hubungan identitas karena 0merupakan elemen netral dalam penambahan pointer-integer). Bagaimanapun, p[i]didefinisikan sebagai *(p+i). Jawabannya membuat poin yang bagus.
Peter - Pulihkan Monica
typeof(*p)dan typeof(p[0])keduanya charjadi tidak banyak lagi yang bisa berbeda. Saya setuju bahwa 'tampaknya berperilaku sama' bukanlah kata-kata yang terbaik, karena semantiknya sangat berbeda. Posting Anda mengingatkan saya cara terbaik untuk elemen akses C ++ array: 0[p], 1[p], 2[p]dll Ini adalah bagaimana pro melakukannya, setidaknya ketika mereka ingin membingungkan orang yang lahir setelah bahasa pemrograman C.
tobi_s
Ini menarik, dan saya tergoda untuk menambahkan tautan ke C FAQ, tetapi saya menyadari bahwa ada banyak pertanyaan terkait, tetapi tampaknya tidak ada yang langsung mengarah ke pertanyaan ini di sini.
tobi_s
23

Apakah compiler memilih untuk menggunakan lokasi string yang sama untuk Adan Btergantung pada implementasinya. Secara formal, Anda dapat mengatakan bahwa perilaku kode Anda tidak ditentukan .

Kedua pilihan tersebut mengimplementasikan standar C ++ dengan benar.

Batsyeba
sumber
Perilaku kode adalah untuk membuat pengecualian, atau tidak melakukan apa pun, dipilih, sebelum pertama kali kode dijalankan, dengan cara yang tidak ditentukan . Itu tidak berarti perilaku secara keseluruhan tidak ditentukan - hanya saja kompilator dapat memilih salah satu perilaku dengan cara apa pun yang dianggapnya sesuai sebelum perilaku tersebut pertama kali diamati.
supercat
3

Ini adalah pengoptimalan untuk menghemat ruang, sering disebut "penggabungan string". Berikut adalah dokumen untuk MSVC:

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

Oleh karena itu, jika Anda menambahkan / GF ke baris perintah, Anda akan melihat perilaku yang sama dengan MSVC.

Ngomong-ngomong, Anda mungkin tidak boleh membandingkan string melalui pointer seperti itu, alat analisis statis apa pun yang layak akan menandai kode itu sebagai rusak. Anda perlu membandingkan apa yang mereka tunjuk, bukan nilai penunjuk yang sebenarnya.

paulm
sumber