Neden (sadece) bazı derleyiciler aynı dize değişmezleri için aynı adresi kullanıyor?


92

https://godbolt.org/z/cyBiWY

'some'MSVC tarafından oluşturulan assembler kodunda iki değişmezi görebiliyorum , ancak sadece bir tanesi clang ve gcc ile. Bu, kod yürütmenin tamamen farklı sonuçlarına yol açar.

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

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

Bu derleme çıktıları arasındaki farkı ve benzerlikleri açıklayan var mı? Optimizasyon talep edilmediğinde bile clang / gcc bir şeyi neden optimize ediyor? Bu bir tür tanımlanmamış davranış mı?

Ayrıca, bildirimleri aşağıda gösterilenlerle değiştirirsem, clang / gcc / msvc'nin "some"assembler kodunda hiç bırakmadığını da fark ettim . Davranış neden farklı?

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

4
stackoverflow.com/a/52424271/1133179 Yakından ilgili bir soruya standart alıntılarla bazı güzel ve alakalı yanıtlar.
luk32


6
MSVC için, / GF derleyici seçeneği bu davranışı denetler. Docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd

1
Bilginize, bu işlevler için de olabilir.
user541686

Yanıtlar:


109

Bu tanımsız bir davranış değil, belirtilmemiş bir davranıştır. İçin dize hazır ,

Derleyicinin, eşit veya örtüşen dize değişmezleri için depolamayı birleştirmesine izin verilir, ancak gerekli değildir. Bu, aynı dize değişmez değerlerinin, işaretçi ile karşılaştırıldığında eşit karşılaştırabileceği veya olmayabileceği anlamına gelir.

Bu, sonucunun bağlı A == Bolmadığı trueveya olabileceği anlamına gelir false.

Standarttan [lex.string] / 16 :

Tüm dize değişmez değerlerinin farklı olup olmadığı (yani örtüşmeyen nesnelerde depolanıp depolanmadığı) ve bir dizge-değişmez değerinin ardışık değerlendirmelerinin aynı mı yoksa farklı bir nesne mi vereceği belirtilmemiştir.


36

Diğer cevaplar, işaretçi adreslerinin neden farklı olmasını bekleyemeyeceğinizi açıkladı. Yine de bunu garanti edecek Ave Beşit olmayan bir şekilde kolayca yeniden yazabilirsiniz :

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

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

Aradaki fark şudur Ave Bartık karakter dizileridir. Bu, işaretçi olmadıkları ve adreslerinin tıpkı iki tam sayı değişkeninin olması gerektiği gibi farklı olması gerektiği anlamına gelir. C ++ bunu karıştırır çünkü işaretçilerin ve dizilerin birbirinin yerine geçebilir görünmesine neden olur ( operator*ve operator[]aynı şekilde davranır gibi görünür), ancak gerçekten farklıdırlar. Örneğin const char *A = "foo"; A++;, benzeri bir şey tamamen yasaldır, ancak const char A[] = "bar"; A++;değildir.

Farkı düşünmenin bir yolu char A[] = "...", "bana bir bellek bloğu ver ve onu ...takip eden karakterlerle doldur \0" derken char *A= "...", "bana, ...ardından gelen karakterleri bulabileceğim bir adres ver" diyor \0.


8
Neden farklı olduğunu açıklayabilirseniz, bu daha da iyi bir cevap olacaktır .
Mark Ransom

Not, *pve p[0]sadece fakat tanıma göre "aynı şekilde davranır gibi" değildir olan (şartıyla aynı p+0 == piçin bir kimlik ilişkidir 0işaretçi tamsayı ek olarak nötr bir unsurdur). Sonuçta p[i]olarak tanımlanır *(p+i). Cevap yine de iyi bir noktaya işaret ediyor.
Peter - Monica'yı yeniden

typeof(*p)ve typeof(p[0])ikisi de charöyle , yani gerçekten farklı olabilecek pek bir şey kalmadı. "Aynı şekilde davranıyor gibi görünmek" in en iyi ifade olmadığına katılıyorum, çünkü anlambilim çok farklı. Yayınınız C ++ Dizilerin erişim elemanlarına en iyi şekilde hatırlattı: 0[p], 1[p], 2[p]vb Bu da C programlama dilinde sonra doğmuş insanları şaşırtmak istediğinizde artıları, en azından böyle yaparız.
tobi_s


Bu ilginç ve C SSS'ye bir bağlantı eklemek istedim, ancak pek çok ilgili soru olduğunu fark ettim, ancak hiçbiri bu sorunun tam olarak burada kaldığı noktaya gelmiyor.
tobi_s

23

Olsun ya da olmasın bir derleyici seçer için aynı dize konumu kullanmaya Ave Buygulamaya bağlıdır. Resmi olarak, kodunuzun davranışının belirtilmediğini söyleyebilirsiniz .

Her iki seçenek de C ++ standardını doğru şekilde uygular.


Kodun davranışı, kodun ilk kez çalıştırılmasından önce, belirtilmemiş bir şekilde bir istisna atmak veya hiçbir şey yapmamaktır . Bu, bir bütün olarak davranışın belirtilmemiş olduğu anlamına gelmez - yalnızca derleyicinin davranış ilk kez gözlemlenmeden önce uygun gördüğü herhangi bir davranışı seçebileceği anlamına gelir.
supercat

3

Genellikle "dizi havuzu" olarak adlandırılan yerden tasarruf etmek için yapılan bir optimizasyondur. İşte MSVC için belgeler:

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

Bu nedenle, komut satırına / GF eklerseniz, MSVC ile aynı davranışı görmeniz gerekir.

Bu arada, muhtemelen dizeleri böyle işaretçilerle karşılaştırmamalısınız, herhangi bir düzgün statik analiz aracı bu kodu hatalı olarak işaretleyecektir. Gerçek işaretçi değerlerini değil, neye işaret ettiklerini karşılaştırmanız gerekir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.