Bangun pengecualian standar dengan argumen penunjuk nol dan kondisi akhir yang tidak mungkin

9

Pertimbangkan program berikut:

#include<stdexcept>
#include<iostream>

int main() {
    try {
        throw std::range_error(nullptr);
    } catch(const std::range_error&) {
        std::cout << "Caught!\n";
    }
}

GCC dan Dentang dengan libstdc ++ panggilan std::terminatedan batalkan program dengan pesan

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Dibatasi dengan libc ++ segfault pada konstruksi pengecualian.

Lihat godbolt .

Apakah kompiler berperilaku sesuai standar? Bagian yang relevan dari standar [diagnosticics.range.error] (C ++ 17 N4659) memang mengatakan bahwa std::range_errormemiliki const char*kelebihan konstruktor yang harus dipilih lebih dari const std::string&kelebihan. Bagian ini juga tidak menyatakan prasyarat apa pun pada konstruktor dan hanya menyatakan prasyarat tersebut

Postconditions : strcmp(what(), what_­arg) == 0.

Postcondition ini selalu memiliki perilaku yang tidak terdefinisi jika what_argadalah null pointer, jadi apakah ini berarti bahwa program saya juga memiliki perilaku yang tidak terdefinisi dan bahwa kedua kompiler bertindak sesuai? Jika tidak, bagaimana seharusnya seseorang membaca postconditions yang tidak mungkin dalam standar?


Setelah dipikir-pikir, saya pikir itu harus berarti perilaku yang tidak terdefinisi untuk program saya, karena jika tidak maka pointer (valid) yang tidak menunjuk ke string null juga akan diizinkan, yang jelas tidak masuk akal.

Jadi, dengan asumsi itu benar, saya ingin memfokuskan pertanyaan lebih pada bagaimana standar menyiratkan perilaku yang tidak terdefinisi ini. Apakah itu mengikuti dari ketidakmungkinan postkondisi bahwa panggilan itu juga memiliki perilaku yang tidak terdefinisi atau apakah prasyarat itu dilupakan begitu saja?


Terinspirasi oleh pertanyaan ini .

kenari
sumber
Kedengarannya seperti std :: range_error diizinkan untuk menyimpan sesuatu dengan referensi, jadi saya tidak akan terkejut. Menelepon what()saat nullptrditeruskan mungkin akan menyebabkan masalah.
Chipster
@Chipster Saya tidak yakin apa yang sebenarnya Anda maksudkan, tetapi setelah memikirkan hal ini lagi, saya pikir itu pasti perilaku yang tidak terdefinisi. Saya telah mengedit pertanyaan saya untuk lebih fokus pada bagaimana kata-kata standar menyiratkan perilaku yang tidak terdefinisi.
walnut
Jika nullptrdilewati, saya akan berpikir bahwa saya what()harus melakukan dereferensi di beberapa titik untuk mendapatkan nilai. Itu akan menjadi dereferencing nullptr, yang paling bermasalah dan pasti untuk crash adalah yang terburuk.
Chipster
Tapi saya setuju. Itu harus perilaku yang tidak terdefinisi. Namun, memasukkan itu ke dalam jawaban yang memadai menjelaskan mengapa di luar kemampuan saya.
Chipster
Saya pikir ini dimaksudkan bahwa itu adalah prasyarat bahwa argumen menunjuk ke string C yang valid, karena strcmpdigunakan untuk menggambarkan nilai what_arg. Itulah yang dikatakan bagian yang relevan dari standar C , yang disebut dengan spesifikasi <cstring>. Tentu saja kata-katanya bisa lebih jelas.
LF

Jawaban:

0

Dari dokumen :

Karena menyalin std :: range_error tidak diizinkan untuk melempar pengecualian, pesan ini biasanya disimpan secara internal sebagai string yang dihitung-referensi yang dialokasikan secara terpisah. Ini juga mengapa tidak ada konstruktor yang mengambil std :: string &&: itu tetap harus menyalin konten.

Ini menunjukkan mengapa Anda mendapatkan segfault, api benar-benar memperlakukannya sebagai string nyata. Secara umum di cpp jika ada yang opsional, akan ada konstruktor / fungsi yang berlebihan yang tidak mengambil apa yang tidak perlu. Jadi melintas nullptruntuk suatu fungsi yang tidak mendokumentasikan sesuatu menjadi opsional akan menjadi perilaku yang tidak terdefinisi. Biasanya API tidak mengambil pointer dengan pengecualian untuk string C. Jadi IMHO aman untuk mengasumsikan melewati nullptr untuk fungsi yang mengharapkan const char *, akan menjadi perilaku yang tidak terdefinisi. API yang lebih baru mungkin lebih suka std::string_viewuntuk kasus-kasus itu.

Memperbarui:

Biasanya adil untuk mengasumsikan C ++ API mengambil pointer untuk menerima NULL. Namun string C adalah kasus khusus. Hingga std::string_view, tidak ada cara yang lebih baik untuk melewatinya dengan efisien. Secara umum untuk menerima API const char *, asumsi harus adalah bahwa itu harus menjadi string C yang valid. yaitu pointer ke urutan chars yang berakhir dengan '\ 0'.

range_errordapat memvalidasi bahwa pointer tidak nullptrtetapi itu tidak dapat memvalidasi jika itu diakhiri dengan '\ 0'. Jadi lebih baik tidak melakukan validasi apa pun.

Saya tidak tahu persis kata-kata dalam standar, tetapi pra-kondisi ini mungkin diasumsikan secara otomatis.

balki
sumber
-2

Ini kembali ke pertanyaan dasar apakah OK untuk membuat std :: string dari nullptr? dan apa yang harus dilakukan?

www.cplusplus.com mengatakan

Jika s adalah pointer nol, jika n == npos, atau jika rentang yang ditentukan oleh [pertama, terakhir) tidak valid, itu menyebabkan perilaku tidak terdefinisi.

Jadi ketika

throw std::range_error(nullptr);

disebut implementasi mencoba membuat sesuatu seperti

store = std::make_shared<std::string>(nullptr);

yang tidak terdefinisi. Yang saya anggap bug (tanpa membaca kata-kata aktual dalam standar). Sebaliknya para pengembang libery bisa membuat sesuatu seperti

if (what_arg)
  store = std::make_shared<std::string>(nullptr);

Tapi kemudian catcher harus memeriksa nullptr what();atau hanya akan crash di sana. Jadi std::range_errorharus menetapkan string kosong atau "(nullptr)" seperti beberapa bahasa lain.

Surt
sumber
"Ini kembali ke pertanyaan dasar apakah boleh membuat string std :: dari nullptr?" - Kurasa tidak.
Konrad Rudolph
Saya tidak berpikir standar menentukan di mana saja bahwa pengecualian harus menyimpan std::stringdan std::stringkonstruktor tidak boleh dipilih dengan resolusi kelebihan beban.
walnut
The const char *kelebihan dipilih oleh resolusi yang berlebihan
MM