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:
[...] Kode ini telah ditinjau oleh para ahli C ++ di seluruh dunia, dan diterbitkan (The C ++ Programming Language, edisi ke- 4 .) Namun, kerentanannya terhadap urutan evaluasi yang tidak ditentukan baru ditemukan baru-baru ini oleh sebuah alat [.. .]
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 replaceharus 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:
s.find( "even" )
dan:
s.find( " don't" )
yang diurutkan secara tidak pasti sehubungan dengan:
s.replace(0, 4, "" )
dua findpanggilan dapat dievaluasi sebelum atau sesudah replace, yang penting karena memiliki efek samping sdengan cara yang akan mengubah hasil find, itu mengubah panjang s. Jadi bergantung pada kapan itu replacedievaluasi relatif terhadap dua findpanggilan, hasilnya akan berbeda.
Jika kita melihat ekspresi rangkaian dan memeriksa urutan evaluasi dari beberapa sub-ekspresi:
s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^ ^ ^ ^ ^ ^ ^ ^
A B | | | C | | |
1 2 3 4 5 6
dan:
.replace( s.find( " don't" ), 6, "" );
^ ^ ^ ^
D | | |
7 8 9
Catatan, kami mengabaikan fakta itu 4dan 7selanjutnya dapat dipecah menjadi lebih banyak sub-ekspresi. Begitu:
Adiurutkan sebelum Bdiurutkan sebelum Cdiurutkan sebelumnyaD
1to 9tidak pasti diurutkan sehubungan dengan sub-ekspresi lain dengan beberapa pengecualian yang tercantum di bawah ini
1untuk 3diurutkan sebelumnyaB
4untuk 6diurutkan sebelumnyaC
7untuk 9diurutkan sebelumnyaD
Kunci dari masalah ini adalah:
4ke 9tidak pasti diurutkan sehubungan denganB
Urutan potensial pilihan evaluasi untuk 4dan 7sehubungan dengan Bmenjelaskan perbedaan hasil antara clangdan gccsaat mengevaluasi f2(). Dalam tes saya clangmengevaluasi Bsebelum mengevaluasi 4dan 7saat gccmengevaluasinya setelah. Kami dapat menggunakan program pengujian berikut untuk mendemonstrasikan apa yang terjadi dalam setiap kasus:
#include <iostream>
#include <string>
std::string::size_type my_find( std::string s, const char *cs )
{
std::string::size_type pos = s.find( cs ) ;
std::cout << "position " << cs << " found in complete expression: "
<< pos << std::endl ;
return pos ;
}
int main()
{
std::string s = "but I have heard it works even if you don't believe in it" ;
std::string copy_s = s ;
std::cout << "position of even before s.replace(0, 4, \"\" ): "
<< s.find( "even" ) << std::endl ;
std::cout << "position of don't before s.replace(0, 4, \"\" ): "
<< s.find( " don't" ) << std::endl << std::endl;
copy_s.replace(0, 4, "" ) ;
std::cout << "position of even after s.replace(0, 4, \"\" ): "
<< copy_s.find( "even" ) << std::endl ;
std::cout << "position of don't after s.replace(0, 4, \"\" ): "
<< copy_s.find( " don't" ) << std::endl << std::endl;
s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
.replace( my_find( s, " don't" ), 6, "" );
std::cout << "Result: " << s << std::endl ;
}
Hasil untuk gcc( lihat langsung )
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
Hasil untuk clang( lihat langsung ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position even found in complete expression: 22
position don't found in complete expression: 33
Result: I have heard it works only if you believe in it
Hasil untuk Visual Studio( lihat langsung ):
position of even before s.replace(0, 4, "" ): 26
position of don't before s.replace(0, 4, "" ): 37
position of even after s.replace(0, 4, "" ): 22
position of don't after s.replace(0, 4, "" ): 33
position don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it
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:
Kecuali jika disebutkan, evaluasi operan operator individu dan subekspresi ekspresi individu tidak diurutkan. [...]
dan kita tahu bahwa panggilan fungsi memperkenalkan hubungan urutan sebelum fungsi memanggil ekspresi dan argumen postfix sehubungan dengan badan fungsi, dari bagian 1.9:
[...] Saat memanggil suatu fungsi (apakah fungsinya sebaris atau tidak), setiap perhitungan nilai dan efek samping yang terkait dengan ekspresi argumen apa pun, atau dengan ekspresi postfix yang menunjukkan fungsi yang dipanggil, diurutkan sebelum eksekusi setiap ekspresi atau pernyataan di tubuh fungsi yang disebut. [...]
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:
[...] Ekspresi postfix sebelum titik atau panah dievaluasi; 64
hasil evaluasi itu, bersama dengan ekspresi-id, menentukan hasil dari seluruh ekspresi postfix.
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 dari 5.2 ekspresi Postfix :
postfix-expression:
postfix-expression ( expression-listopt) // function call
postfix-expression . templateopt id-expression // Class member access, ends
// up as a postfix-expression
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:
Ekspresi-postfix diurutkan sebelum setiap ekspresi dalam daftar ekspresi dan argumen default apa pun . Inisialisasi parameter, termasuk setiap penghitungan nilai terkait dan efek samping, diurutkan secara tidak pasti sehubungan dengan parameter lainnya. [Catatan: Semua efek samping evaluasi argumen diurutkan sebelum fungsi dimasukkan (lihat 4.6). —End note] [Contoh:
void f() {
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"); // OK
}
—Dan contoh]
s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );