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 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:
s.find( "even" )
dan:
s.find( " don't" )
yang diurutkan secara tidak pasti sehubungan dengan:
s.replace(0, 4, "" )
dua find
panggilan dapat dievaluasi sebelum atau sesudah replace
, yang penting karena memiliki efek samping s
dengan cara yang akan mengubah hasil find
, itu mengubah panjang s
. Jadi bergantung pada kapan itu replace
dievaluasi relatif terhadap dua find
panggilan, 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 4
dan 7
selanjutnya dapat dipecah menjadi lebih banyak sub-ekspresi. Begitu:
A
diurutkan sebelum B
diurutkan sebelum C
diurutkan sebelumnyaD
1
to 9
tidak pasti diurutkan sehubungan dengan sub-ekspresi lain dengan beberapa pengecualian yang tercantum di bawah ini
1
untuk 3
diurutkan sebelumnyaB
4
untuk 6
diurutkan sebelumnyaC
7
untuk 9
diurutkan sebelumnyaD
Kunci dari masalah ini adalah:
4
ke 9
tidak pasti diurutkan sehubungan denganB
Urutan potensial pilihan evaluasi untuk 4
dan 7
sehubungan dengan B
menjelaskan perbedaan hasil antara clang
dan gcc
saat mengevaluasi f2()
. Dalam tes saya clang
mengevaluasi B
sebelum mengevaluasi 4
dan 7
saat gcc
mengevaluasinya 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, "" );