Apakah `string.assign (string.data (), 5)` didefinisikan dengan baik atau UB?

11

Seorang rekan kerja ingin menulis ini:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

Saya mengatakan bahwa kembali string_viewmembuat saya tidak enak a priori , dan lebih jauh, aliasing di sini tampak seperti UB bagi saya.

Saya dapat mengatakan dengan pasti bahwa line = strip_whitespace(line)dalam hal ini setara dengan line = std::string_view(line.data(), 5). Saya percaya bahwa akan memanggil string::operator=(const T&) [with T=string_view], yang didefinisikan setara dengan line.assign(const T&) [with T=string_view], yang didefinisikan setara dengan line.assign(line.data(), 5), yang didefinisikan untuk melakukan ini:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Tapi ini tidak mengatakan apa yang terjadi ketika ada alias.

Saya menanyakan pertanyaan ini di Slack cpplang kemarin dan mendapat jawaban beragam. Mencari jawaban yang super otoritatif di sini, dan / atau analisis empiris implementasi vendor perpustakaan nyata.


Saya menulis uji kasus untuk string::assign, vector::assign, deque::assign, list::assign, dan forward_list::assign.

  • Libc ++ membuat semua test case ini berfungsi.
  • Libstdc ++ membuat semuanya berfungsi kecuali forward_list, yang segfaults.
  • Saya tidak tahu tentang perpustakaan MSVC.

Segfault di libstdc ++ memberi saya harapan bahwa ini adalah UB; tapi saya juga melihat libc ++ dan libstdc ++ akan berusaha keras untuk membuat ini bekerja setidaknya dalam kasus umum.

Quuxplusone
sumber
Apakah Anda mengkompilasi kasus uji dengan ASan dan / atau menjalankannya di bawah Valgrind? Itu akan menghilangkan dugaan apakah kode menyebabkan pelanggaran akses, meskipun mungkin masih berfungsi dalam praktiknya bukan berdasarkan definisi.
Konrad Rudolph
1
"Jika ada fungsi anggota atau operator dari basic_string melempar pengecualian, fungsi atau operator itu tidak memiliki efek lain pada objek basic_string." - ini memaksa alokasi penyimpanan terjadi sebelum penyimpanan yang ada dibebaskan, sehingga pengecualian dilemparkan jika alokasi gagal, tanpa mengubah *this. Tapi saya tidak melihat apa pun untuk mencegah penyimpanan yang ada digunakan kembali, dalam hal ini menjadi tidak ditentukan, karena semantik copy-overing penyimpanan tidak ditentukan.
Sam Varshavchik
2
Untuk peti kemas urutan yang disebutkan, sudah pasti UB, karena prasyarat pelanggaran assignpersyaratan di [tab: container.seq.req] .
kenari

Jawaban:

8

Kecuali beberapa pengecualian yang bukan milik Anda, memanggil fungsi anggota non-const (yaitu assign) pada string membatalkan [...] pointer [...] ke elemen-elemennya. Ini melanggar prasyarat tentang assignitu [s, s + n)adalah rentang yang valid, jadi ini adalah perilaku yang tidak terdefinisi.

Perhatikan bahwa string::operator=(string const&)memiliki bahasa yang khusus untuk menjadikan penugasan mandiri sebagai larangan.

ecatmur
sumber
1
Jadi apa sebenarnya titik pembatalan dan titik di mana prasyarat diperlukan untuk dipegang? Jawabannya tampaknya mengasumsikan bahwa prasyarat harus dipegang setelah fungsi anggota dipanggil.
kenari
1
@walnut Saya bukan pengacara bahasa (bukan orang dengan pengetahuan C ++ yang diperpanjang), tetapi ketika kami membalikkan skenario Anda, kami dapat mengajukan pertanyaan - dapatkah rentang tersebut dibatalkan selama eksekusi assign? Jika ya, maka kita harus menetapkan titik tertentu di dalam penerapan assign to mark ketika tepatnya pembatalan dapat terjadi, dan saya percaya itu bukan sesuatu yang akan dilakukan C ++. Saya bisa saja salah.
Fureeish
2
@Fureeish Saya juga tidak tahu, tapi lihat misalnya masalah LWG 526 , ditutup sebagai " bukan cacat ", yang menyebutkan dalam rekomendasinya untuk penutupan yang std::vector::insert(iterator pos, const T& value)harus bekerja jika valuemasuk ke dalam vektor itu sendiri, karena standar tidak menentukan bahwa itu diizinkan untuk tidak berfungsi, meskipun referensi itu mungkin dibatalkan oleh panggilan.
kenari
1
@walnut " diperlukan untuk bekerja karena standar tidak memberikan izin agar tidak berfungsi. " - suka saja . Sooo ... apakah pantas untuk bertanya apa yang terjadi dalam praktik ? Apakah implementasi diperlukan untuk membuat salinan argumen dalam situasi seperti itu? Bagaimana Anda bisa mengimplementasikannya secara realistis ..? Saya pernah mendengar tentang standar yang membutuhkan kompiler untuk melakukan yang tidak mungkin - apakah ini salah satu kasus? Bagaimanapun, terima kasih atas komentarnya!
Fureeish
1
@Fureeish Sebenarnya contoh saya sebelumnya (sekarang dihapus) sebenarnya tidak menguji apa yang ingin saya uji. Berikut adalah contoh tetap yang menunjukkan bahwa baik libc ++ dan libstdc ++ benar-benar melakukan copy sebelum pindah pada realokasi sebagaimana diperlukan.
kenari