Ne i = i ++ + 1 yapan; C ++ 17 yasal?


186

Tanımsız davranışlar bağırmaya başlamadan önce, bu açıkça N4659'da listelenir (C ++ 17)

  i = i++ + 1;        // the value of i is incremented

Yine de N3337'de (C ++ 11)

  i = i++ + 1;        // the behavior is undefined

Ne değişti?

Anladığım kadarıyla, gelen [N4659 basic.exec]

Belirtilen durumlar dışında, münferit operatörlerin işlenenlerinin ve münferit ifadelerin alt ifadelerinin değerlendirmeleri birbirinden bağımsız değildir. [...] Bir operatörün işlenenlerinin değer hesaplamaları, operatörün sonucunun değer hesaplamasından önce sıralanır. Bir bellek konumundaki bir yan etki, aynı bellek konumundaki başka bir yan etkiye veya aynı bellek konumundaki herhangi bir nesnenin değerini kullanan bir değer hesaplamasına göre sıralanmamışsa ve bunlar potansiyel olarak eşzamanlı değilse, davranış tanımsızdır.

Burada değeri de tanımlanmıştır [N4659 basic.type]

Önemsiz olarak kopyalanabilen türler için değer temsili, nesne temsili içinde, bir uygulama tarafından tanımlanan bir değer kümesinin ayrı bir öğesi olan bir değeri belirleyen bir bitler kümesidir

Kaynaktan [N3337 basic.exec]

Belirtilen durumlar dışında, münferit operatörlerin işlenenlerinin ve münferit ifadelerin alt ifadelerinin değerlendirmeleri birbirinden bağımsız değildir. [...] Bir operatörün işlenenlerinin değer hesaplamaları, operatörün sonucunun değer hesaplamasından önce sıralanır. Bir skaler nesne üzerindeki bir yan etki, aynı skaler nesne üzerindeki başka bir yan etkiye veya aynı skaler nesnenin değerini kullanan bir değer hesaplamasına göre sıralanmamışsa, davranış tanımsızdır.

Benzer şekilde, değer [N3337 basic.type] ' de tanımlanır

Önemsiz olarak kopyalanabilen tipler için değer temsili, nesne temsili içinde, uygulama tanımlı bir değer kümesinin ayrı bir öğesi olan bir değeri belirleyen bir bitler kümesidir.

Onlar önemli değil eşzamanlılık denince hariç özdeştir ve kullanımı ile hafıza konumu yerine skaler nesne ,

Aritmetik türler, numaralandırma türleri, işaretçi türleri, işaretçi-üye türleri std::nullptr_tve bu türlerin cv-nitelikli sürümleri topluca skaler tipler olarak adlandırılır.

Hangi örneği etkilemez.

Gönderen [N4659 expr.ass]

Atama işleci (=) ve bileşik atama işleçlerinin tümü sağdan sola gruplandırılır. Hepsi sol işlenen olarak değiştirilebilir bir değer gerektirir ve sol işlenene referansla bir değer döndürür. Sol işlenen bir bit alanı ise, her durumda sonuç bir bit alanıdır. Her durumda, atama, sağ ve sol işlenenlerin değer hesaplamasından sonra ve atama ifadesinin değer hesaplamasından önce sıralanır. Sağ işlenen, sol işlenenden önce sıralanır.

Gönderen [N3337 expr.ass]

Atama işleci (=) ve bileşik atama işleçlerinin tümü sağdan sola gruplandırılır. Hepsi sol işlenen olarak değiştirilebilir bir değer gerektirir ve sol işlenene referansla bir değer döndürür. Sol işlenen bir bit alanı ise, her durumda sonuç bir bit alanıdır. Her durumda, atama, sağ ve sol işlenenlerin değer hesaplamasından sonra ve atama ifadesinin değer hesaplamasından önce sıralanır.

Tek fark N3337'de bulunmayan son cümledir.

Sol işlenen olarak son cümle ancak, herhangi bir öneme sahip olmamalıdır ine olduğunu "Başka bir yan etki" ne de "aynı skaler nesnenin değerini kullanarak" olarak id-ifadesi bir lvalue olduğunu.


23
Nedenini belirlediniz: C ++ 17'de, sağ işlenen sol işlenenden önce sıralanır. C ++ 11'de böyle bir sıralama yoktu. Sorunuz tam olarak nedir?
Robᵩ

4
@ Robᵩ Son cümleyi görün.
Passer

7
Herkesin bu değişimin motivasyonuyla bağlantısı var mı? Statik bir analizör gibi kod ile karşı karşıya geldiğinde "bunu yapmak istemiyorum" diyebilmek istiyorum i = i++ + 1;.

7
@NeilButterworth, bu p0145r3.pdf belgesinden alınmıştır : "Deyimsel C ++ için İfade Değerlendirme Siparişinin İncelenmesi".
xaizek

9
@NeilButterworth, bölüm 2, bunun karşı sezgisel olduğunu ve uzmanların bile her durumda doğru şeyi yapamadığını söylüyor. Tüm motivasyonları bu kadar.
xaizek

Yanıtlar:


144

C ++ 11'de "atama" eylemi, yani LHS'yi değiştirmenin yan etkisi , sağ işlenenin değer hesaplamasından sonra sıralanır . Bunun göreceli olarak "zayıf" bir garanti olduğuna dikkat edin: sadece RHS'nin değer hesaplamasıyla ilgili sıralama üretir . Yan etkilerin ortaya çıkması değer hesaplamasının bir parçası olmadığından, RHS'de mevcut olabilecek yan etkiler hakkında hiçbir şey söylemez . C ++ 11'in gereksinimleri, atama eylemi ile RHS'nin herhangi bir yan etkisi arasında nispi bir sıralama oluşturmaz. UB için potansiyel yaratan da budur.

Bu durumda tek umut, RHS'de kullanılan belirli operatörler tarafından yapılan ek garantilerdir. RHS bir önek kullansaydı ++, önek biçimine özgü sıralama özellikleri ++bu örnekte günü kurtarmış olurdu. Ancak postfix ++farklı bir hikaye: böyle bir garanti vermiyor. C ++ 11'de =ve postfix'in yan etkileri ++bu örnekte birbiriyle ilişkili olarak sonuçlanmamıştır. Ve bu UB.

C ++ 17'de atama işlecinin belirtimine fazladan bir cümle eklenir:

Sağ işlenen, sol işlenenden önce sıralanır.

Yukarıdakilerle birlikte çok güçlü bir garanti sağlar. RHS'de meydana gelen her şeyi (herhangi bir yan etki dahil) LHS'de gerçekleşen her şeyden önce sıralar . Gerçek atama LHS'den (ve RHS) sonra sıralandığından, bu ekstra sıralama, atama hareketini RHS'de bulunan herhangi bir yan etkiden tamamen izole eder. Bu daha güçlü sıralama, yukarıdaki UB'yi ortadan kaldıran şeydir.

(@John Bollinger'in yorumlarını dikkate alacak şekilde güncellendi.)


3
Söz konusu alıntıda "sol işlenen" tarafından kapsanan etkilere "gerçek görevlendirme eylemi" eklemek gerçekten doğru mu? Standart, gerçek ödevin sıralanması hakkında ayrı bir dile sahiptir. Kapsamlı olarak sunduğunuz alıntıyı, bu bölümün geri kalanıyla birlikte yeterli destek vermeyen yeterli görünmeyen sol ve sağ alt ifadelerin sıralanması ile sınırlıdır. OP ifadesinin tanımı.
John Bollinger

11
Düzeltme: Gerçek atama, sol işlenenin değer hesaplamasından sonra hala dizilenir ve sol işlenenin değerlendirilmesi, sağ işlenenin (tam) değerlendirilmesinden sonra dizilir, bu nedenle evet, OP'nin iyi tanımlanmışlığını desteklemek için bu değişiklik yeterlidir hakkında sormak. Ben sadece ayrıntıları tartışıyorum, ama farklı kod için farklı etkileri olabilir, çünkü bunlar önemlidir.
John Bollinger

3
@JohnBollinger: Standardın yazarlarının, basit kod üretiminin verimliliğini bozan ve tarihsel olarak gerekli olmayan bir değişiklik yapacağını ve henüz yokluğu çok daha büyük bir sorun olan diğer davranışları tanımlamaktan çekineceğini merak ediyorum. verimlilik için nadiren anlamlı bir engel teşkil eder.
supercat

1
@Kaz: Bileşik atamaları için sağ yan sonra sol taraf değeri değerlendirmenin yapılmasında gibi bir sağlar x -= y;olarak işlenecek mov eax,[y] / sub [x],eaxyerine mov eax,[x] / neg eax / add eax,[y] / mov [x],eax. Bununla ilgili iddialı bir şey görmüyorum. Bir sipariş belirtmek zorunda olsaydı, en etkili sıralama muhtemelen önce sol taraftaki nesneyi tanımlamak için gerekli tüm hesaplamaları yapmak , daha sonra sağ işleneni, sonra sol nesnenin değerini değerlendirmek olurdu, ancak bu bir terim olmasını gerektirir sol nesnenin kimliğini çözme eylemi için.
supercat

1
@Kaz: Eğer xve ywere volatile, o yan etkileri olur. Ayrıca, aynı hususlar için de geçerli olacak x += f();nerede, f()tuşelere x.
supercat

33

Yeni cümleyi tanımladın

Sağ işlenen, sol işlenenden önce sıralanır.

ve sol işlenenin bir değer olarak değerlendirilmesinin doğru olmadığını doğru bir şekilde belirlediniz. Bununla birlikte, daha önce dizilenenlerin geçişli bir ilişki olduğu belirtilmektedir. Bu nedenle, tam sağ işlenen (artım sonrası dahil) de atamadan önce sıralanır. C ++ 11'de, yalnızca sağ işlenenin değer hesaplaması atamadan önce sıralanmıştır.


7

Eski C ++ standartlarında ve C11'de, atama operatörü metninin tanımı metinle biter:

İşlenenlerin değerlendirmeleri sıralanmamıştır.

Yani, işlenenlerdeki yan etkiler sıralanmamıştır ve bu nedenle aynı değişkeni kullanırlarsa kesinlikle tanımlanmamış davranışlardır.

Bu metin C ++ 11'de basitçe kaldırıldı ve biraz belirsiz kaldı. UB mi yoksa değil mi? Bu, ekledikleri C ++ 17'de açıklığa kavuşturuldu:

Sağ işlenen, sol işlenenden önce sıralanır.


Bir yan not olarak, daha eski standartlarda bile, bunların hepsi C99'dan çok açıktı:

İşlenenlerin değerlendirme sırası belirtilmemiştir. Bir atama operatörünün sonucunu değiştirmek veya bir sonraki sıra noktasından sonra ona erişmek için girişimde bulunulursa, davranış tanımsızdır.

Temel olarak, C11 / C ++ 11'de, bu metni kaldırdıklarında berbat ettiler.


1

Bu diğer cevaplar hakkında daha fazla bilgi ve ben aşağıdaki kod genellikle de sorulur gibi ben gönderiyorum .

Diğer cevaplardaki açıklama doğrudur ve şu anda iyi tanımlanmış (ve depolanan değerini değiştirmeyen i) aşağıdaki kod için de geçerlidir :

i = i++;

+ 1Kırmızı ringa ve Standart kendi örneklerde kullandı neden önce belki C ++ ile 11 posta listelerinde savunarak hatırlama insanları her ne kadar gerçekten, açık değil + 1bir fark yarattı nedeniyle sağ erken lvalue dönüşüm zorlayarak için el tarafı. Kesinlikle bunların hiçbiri C ++ 17 için geçerli değildir (ve muhtemelen hiçbir zaman C ++ sürümlerinde uygulanmaz).

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.