Sebelum Anda mulai berteriak perilaku tidak terdefinisi, ini secara eksplisit terdaftar di N4659 (C ++ 17)
i = i++ + 1; // the value of i is incremented
Namun dalam N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Apa yang berubah?
Dari apa yang bisa saya kumpulkan, dari [N4659 basic.exec]
Kecuali jika disebutkan, evaluasi operan dari operator individual dan subekspresi dari ekspresi individu tidak dilakukan. [...] Perhitungan nilai operan operator diurutkan sebelum perhitungan nilai hasil operator. Jika efek samping pada lokasi memori tidak dilakukan relatif terhadap efek samping lain pada lokasi memori yang sama atau perhitungan nilai menggunakan nilai objek apa pun di lokasi memori yang sama, dan mereka tidak berpotensi bersamaan, perilaku tersebut tidak terdefinisi.
Di mana nilai didefinisikan pada [N4659 basic.type]
Untuk tipe yang dapat disalin sepele, representasi nilai adalah sekumpulan bit dalam representasi objek yang menentukan nilai , yang merupakan salah satu elemen diskrit dari serangkaian nilai yang ditentukan implementasi
Dari [N3337 basic.exec]
Kecuali jika disebutkan, evaluasi operan dari operator individual dan subekspresi dari ekspresi individu tidak dilakukan. [...] Perhitungan nilai operan operator diurutkan sebelum perhitungan nilai hasil operator. Jika efek samping pada objek skalar tidak diikutsertakan relatif terhadap efek samping lain pada objek skalar yang sama atau perhitungan nilai menggunakan nilai objek skalar yang sama, perilaku tidak terdefinisi.
Demikian juga, nilai didefinisikan pada [N3337 basic.type]
Untuk tipe yang dapat disalin sepele, representasi nilai adalah sekumpulan bit dalam representasi objek yang menentukan nilai , yang merupakan salah satu elemen diskrit dari serangkaian nilai yang ditentukan implementasi.
Mereka identik kecuali menyebutkan konkurensi yang tidak masalah, dan dengan penggunaan lokasi memori bukannya objek skalar , di mana
Tipe aritmatika, tipe enumerasi, tipe pointer, pointer ke tipe anggota,
std::nullptr_t
,, dan versi yang memenuhi syarat cv dari tipe-tipe ini secara kolektif disebut tipe skalar.
Yang tidak mempengaruhi contoh.
Dari [N4659 expr.ass]
Operator penugasan (=) dan operator penugasan majemuk semuanya grup kanan-ke-kiri. Semua membutuhkan nilai yang dapat dimodifikasi sebagai operan kiri mereka dan mengembalikan nilai yang mengacu pada operan kiri. Hasilnya dalam semua kasus adalah bidang-bit jika operan kiri adalah bidang-sedikit. Dalam semua kasus, penugasan diurutkan setelah perhitungan nilai operan kanan dan kiri, dan sebelum perhitungan nilai ekspresi penugasan. Operan kanan diurutkan sebelum operan kiri.
Dari [N3337 expr.ass]
Operator penugasan (=) dan operator penugasan majemuk semuanya grup kanan-ke-kiri. Semua membutuhkan nilai yang dapat dimodifikasi sebagai operan kiri mereka dan mengembalikan nilai yang mengacu pada operan kiri. Hasilnya dalam semua kasus adalah bidang-bit jika operan kiri adalah bidang-sedikit. Dalam semua kasus, penugasan diurutkan setelah perhitungan nilai operan kanan dan kiri, dan sebelum perhitungan nilai ekspresi penugasan.
Satu-satunya perbedaan adalah kalimat terakhir yang tidak ada di N3337.
Namun kalimat terakhir, seharusnya tidak memiliki kepentingan karena operan kiri i
bukanlah "efek samping lain" atau "menggunakan nilai objek skalar yang sama" karena ekspresi id adalah lvalue.
sumber
i = i++ + 1;
.Jawaban:
Dalam C ++ 11 tindakan "penugasan", yaitu efek samping dari memodifikasi LHS, diurutkan setelah perhitungan nilai operan yang tepat. Perhatikan bahwa ini adalah jaminan yang relatif "lemah": hanya menghasilkan urutan dengan kaitannya dengan perhitungan nilai RHS. Ia tidak mengatakan apa-apa tentang efek samping yang mungkin ada dalam RHS, karena terjadinya efek samping bukan bagian dari perhitungan nilai . Persyaratan C ++ 11 tidak menetapkan urutan relatif antara tindakan penugasan dan efek samping RHS. Inilah yang menciptakan potensi bagi UB.
Satu-satunya harapan dalam hal ini adalah jaminan tambahan yang dibuat oleh operator tertentu yang digunakan dalam RHS. Jika RHS menggunakan awalan
++
, mengurutkan properti khusus untuk bentuk awalan++
akan menyimpan hari dalam contoh ini. Tetapi postfix++
adalah cerita yang berbeda: postfix tidak memberikan jaminan seperti itu. Dalam C ++ 11 efek samping dari=
dan postfix++
berakhir tanpa hubungan dengan satu sama lain dalam contoh ini. Dan itu adalah UB.Dalam C ++ 17 kalimat tambahan ditambahkan ke spesifikasi operator penugasan:
Dikombinasikan dengan hal-hal di atas, ini memberikan jaminan yang sangat kuat. Ini mengurutkan semua yang terjadi di RHS (termasuk efek samping) sebelum semua yang terjadi di LHS. Karena penugasan aktual diurutkan setelah LHS (dan RHS), sekuensing ekstra sepenuhnya mengisolasi tindakan penugasan dari setiap efek samping yang ada dalam RHS. Urutan yang lebih kuat inilah yang menghilangkan UB di atas.
(Diperbarui untuk mempertimbangkan komentar @John Bollinger.)
sumber
x -= y;
untuk diprosesmov eax,[y] / sub [x],eax
daripadamov eax,[x] / neg eax / add eax,[y] / mov [x],eax
. Saya tidak melihat sesuatu yang bodoh tentang hal itu. Jika seseorang harus menentukan urutan, urutan paling efisien mungkin akan melakukan semua perhitungan yang diperlukan untuk mengidentifikasi objek sisi kiri terlebih dahulu, kemudian mengevaluasi operan kanan, kemudian nilai objek kiri, tetapi itu akan memerlukan memiliki istilah untuk tindakan menyelesaikan id'ty objek kiri.x
dany
duluvolatile
, itu akan memiliki efek samping. Lebih lanjut, pertimbangan yang sama akan berlaku untukx += f();
, jikaf()
dimodifikasix
.Anda mengidentifikasi kalimat baru
dan Anda mengidentifikasi dengan benar bahwa evaluasi operan kiri sebagai nilai tidak relevan. Namun, diurutkan sebelum ditetapkan menjadi hubungan transitif. Oleh karena itu operan lengkap yang lengkap (termasuk kenaikan-pos) juga diurutkan sebelum penugasan. Dalam C ++ 11, hanya perhitungan nilai operan kanan yang diurutkan sebelum penugasan.
sumber
Dalam standar C ++ yang lebih lama dan dalam C11, definisi teks operator penugasan berakhir dengan teks:
Berarti bahwa efek samping dalam operan tidak diikuti dan karenanya perilaku yang tidak terdefinisi jika mereka menggunakan variabel yang sama.
Teks ini hanya dihapus di C ++ 11, meninggalkannya agak ambigu. Apakah ini UB atau bukan? Ini telah diklarifikasi dalam C ++ 17 di mana mereka menambahkan:
Sebagai catatan tambahan, bahkan dalam standar yang lebih lama, ini semua dibuat sangat jelas, contoh dari C99:
Pada dasarnya, di C11 / C ++ 11, mereka mengacaukan ketika mereka menghapus teks ini.
sumber
Ini adalah informasi lebih lanjut untuk jawaban lain dan saya mempostingnya karena kode di bawah ini sering ditanyakan juga .
Penjelasan dalam jawaban lain benar dan juga berlaku untuk kode berikut yang sekarang didefinisikan dengan baik (dan tidak mengubah nilai yang disimpan
i
):Ini
+ 1
adalah herring merah dan tidak terlalu jelas mengapa Standar menggunakannya dalam contoh mereka, meskipun saya ingat orang berdebat di milis sebelum C ++ 11 yang mungkin+ 1
membuat perbedaan karena memaksa konversi nilai awal di kanan- sisi tangan. Tentu saja tidak ada yang berlaku di C ++ 17 (dan mungkin tidak pernah diterapkan dalam versi C ++).sumber