Dalam Bjarne Stroustrup C ++ Bahasa Pemrograman bagian edisi 4 36.3.6
STL-seperti Operasi kode berikut digunakan sebagai contoh chaining :
void f2()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
.replace( s.find( " don't" ), 6, "" );
assert( s == "I have heard it works only if you believe in it" ) ;
}
Penegasan gagal dalam gcc
( lihat langsung ) dan Visual Studio
( lihat langsung ), tetapi tidak gagal saat menggunakan Clang ( lihat langsung ).
Mengapa saya mendapatkan hasil yang berbeda? Apakah salah satu dari kompiler ini salah mengevaluasi ekspresi rantai atau apakah kode ini menunjukkan beberapa bentuk perilaku yang tidak ditentukan atau tidak ditentukan ?
c++
c++11
language-lawyer
operator-precedence
unspecified-behavior
Shafik Yaghmour
sumber
sumber
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
cout << a << b << c
≡operator<<(operator<<(operator<<(cout, a), b), c)
hanya sedikit kurang jelek.Jawaban:
Kode menunjukkan perilaku yang tidak ditentukan karena urutan evaluasi sub-ekspresi yang tidak ditentukan meskipun tidak memanggil perilaku yang tidak ditentukan karena semua efek samping dilakukan dalam fungsi yang memperkenalkan hubungan urutan antara efek samping dalam kasus ini.
Contoh ini disebutkan dalam proposal N4228: Refining Expression Evaluation Order for Idiomatic C ++ yang mengatakan hal berikut tentang kode dalam pertanyaan:
Detail
Mungkin jelas bagi banyak orang bahwa argumen ke fungsi memiliki urutan evaluasi yang tidak ditentukan tetapi mungkin tidak begitu jelas bagaimana perilaku ini berinteraksi dengan panggilan fungsi yang dirantai. Tidak jelas bagi saya ketika saya pertama kali menganalisis kasus ini dan tampaknya juga tidak bagi semua pengulas ahli .
Sekilas mungkin tampak bahwa karena masing-masing
replace
harus dievaluasi dari kiri ke kanan bahwa grup argumen fungsi yang sesuai harus dievaluasi sebagai grup dari kiri ke kanan juga.Ini tidak benar, argumen fungsi memiliki urutan evaluasi yang tidak ditentukan, meskipun pemanggilan fungsi berantai memperkenalkan urutan evaluasi dari kiri ke kanan untuk setiap pemanggilan fungsi, argumen dari setiap pemanggilan fungsi hanya diurutkan sebelumnya sehubungan dengan pemanggilan fungsi anggota mereka menjadi bagiannya. dari. Secara khusus hal ini memengaruhi panggilan berikut:
dan:
yang diurutkan secara tidak pasti sehubungan dengan:
dua
find
panggilan dapat dievaluasi sebelum atau sesudahreplace
, yang penting karena memiliki efek sampings
dengan cara yang akan mengubah hasilfind
, itu mengubah panjangs
. Jadi bergantung pada kapan itureplace
dievaluasi relatif terhadap duafind
panggilan, hasilnya akan berbeda.Jika kita melihat ekspresi rangkaian dan memeriksa urutan evaluasi dari beberapa sub-ekspresi:
dan:
Catatan, kami mengabaikan fakta itu
4
dan7
selanjutnya dapat dipecah menjadi lebih banyak sub-ekspresi. Begitu:A
diurutkan sebelumB
diurutkan sebelumC
diurutkan sebelumnyaD
1
to9
tidak pasti diurutkan sehubungan dengan sub-ekspresi lain dengan beberapa pengecualian yang tercantum di bawah ini1
untuk3
diurutkan sebelumnyaB
4
untuk6
diurutkan sebelumnyaC
7
untuk9
diurutkan sebelumnyaD
Kunci dari masalah ini adalah:
4
ke9
tidak pasti diurutkan sehubungan denganB
Urutan potensial pilihan evaluasi untuk
4
dan7
sehubungan denganB
menjelaskan perbedaan hasil antaraclang
dangcc
saat mengevaluasif2()
. Dalam tes sayaclang
mengevaluasiB
sebelum mengevaluasi4
dan7
saatgcc
mengevaluasinya setelah. Kami dapat menggunakan program pengujian berikut untuk mendemonstrasikan apa yang terjadi dalam setiap kasus:Hasil untuk
gcc
( lihat langsung )Hasil untuk
clang
( lihat langsung ):Hasil untuk
Visual Studio
( lihat langsung ):Detail dari standar
Kita tahu bahwa kecuali ditentukan evaluasi sub-ekspresi tidak diurutkan, ini dari draft C ++ 11 bagian standar
1.9
Eksekusi program yang mengatakan:dan kita tahu bahwa panggilan fungsi memperkenalkan hubungan urutan sebelum fungsi memanggil ekspresi dan argumen postfix sehubungan dengan badan fungsi, dari bagian
1.9
:Kita juga tahu bahwa akses anggota kelas dan oleh karena itu rangkaian akan dievaluasi dari kiri ke kanan, dari bagian
5.2.5
Akses anggota kelas yang mengatakan:Catatan, dalam kasus di mana ekspresi-id akhirnya menjadi fungsi anggota non-statis, ini tidak menentukan urutan evaluasi daftar ekspresi dalam
()
karena itu adalah sub-ekspresi terpisah. Tata bahasa yang relevan dari5.2
ekspresi Postfix :C ++ 17 perubahan
Proposal p0145r3: Refining Expression Evaluation Order for Idiomatic C ++ membuat beberapa perubahan. Menyertakan perubahan yang memberikan kode perilaku yang ditentukan dengan baik dengan memperkuat urutan aturan evaluasi untuk ekspresi-postfix dan daftar ekspresinya .
[expr.call] p5 mengatakan:
sumber
"even"
,,"don't"
dan beberapa contohs
tidak diurutkan relatif satu sama lain.foo().func( bar() )
, dapat meneleponfoo()
sebelum atau sesudah meneleponbar()
. The postfix-ekspresi adalahfoo().func
. Argumen dan ekspresi postfix diurutkan sebelum isifunc()
, tetapi tidak diurutkan relatif satu sama lain.Hal ini dimaksudkan untuk menambah informasi tentang masalah yang berkaitan dengan C ++ 17. Proposal ( Refining Expression Evaluation Order for Idiomatic C ++ Revision 2 ) untuk
C++17
mengatasi masalah yang mengutip kode di atas adalah sebagai spesimen.Seperti yang disarankan, saya menambahkan informasi yang relevan dari proposal dan kutipan (menyoroti milik saya):
Makalah ini menyarankan untuk mengubah pra-
C++17
aturan pada urutan evaluasi ekspresi yang dipengaruhi olehC
dan telah ada selama lebih dari tiga dekade. Ia mengusulkan bahwa bahasa harus menjamin idiom kontemporer atau berisiko "jebakan dan sumber yang tidak jelas, bug yang sulit ditemukan" seperti yang terjadi dengan contoh kode di atas.Usulan untuk
C++17
adalah untuk mengharuskan setiap ekspresi memiliki urutan evaluasi didefinisikan dengan baik :Kode di atas berhasil dikompilasi menggunakan
GCC 7.1.1
danClang 4.0.0
.sumber