Betrachten Sie das folgende Programm:
#include<stdexcept>
#include<iostream>
int main() {
try {
throw std::range_error(nullptr);
} catch(const std::range_error&) {
std::cout << "Caught!\n";
}
}
GCC und Clang mit libstdc ++ rufen std::terminate
das Programm mit der Nachricht auf und brechen es ab
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Klirren Sie mit libc ++ segfaults beim Aufbau der Ausnahme.
Siehe Godbolt .
Verhalten sich die Compiler standardkonform? Der relevante Abschnitt des Standards [diagnostics.range.error] (C ++ 17 N4659) besagt, dass std::range_error
eine const char*
Konstruktorüberladung vorliegt, die der const std::string&
Überladung vorzuziehen ist . Der Abschnitt enthält auch keine Voraussetzungen für den Konstruktor und nur die Nachbedingung
Nachbedingungen :
strcmp(what(), what_arg) == 0
.
Diese Nachbedingung hat immer ein undefiniertes Verhalten, wenn what_arg
es sich um einen Nullzeiger handelt. Bedeutet dies also, dass mein Programm auch ein undefiniertes Verhalten aufweist und beide Compiler konform handeln? Wenn nicht, wie sollte man solche unmöglichen Nachbedingungen im Standard lesen?
Beim zweiten Gedanken denke ich, dass dies undefiniertes Verhalten für mein Programm bedeuten muss, denn wenn dies nicht der Fall wäre, wären auch (gültige) Zeiger zulässig, die nicht auf nullterminierte Zeichenfolgen zeigen, was eindeutig keinen Sinn ergibt.
Unter der Annahme, dass dies zutrifft, möchte ich die Frage mehr darauf konzentrieren, wie der Standard dieses undefinierte Verhalten impliziert. Folgt aus der Unmöglichkeit der Nachbedingung, dass der Anruf auch ein undefiniertes Verhalten aufweist oder wurde die Vorbedingung einfach vergessen?
Inspiriert von dieser Frage .
quelle
what()
wenn ernullptr
bestanden wird, würde wahrscheinlich Probleme verursachen.nullptr
bestanden wird, würde ich denken, dass eswhat()
irgendwann dereferenzieren müsste, um den Wert zu erhalten. Das wäre eine Dereferenzierung von anullptr
, die bestenfalls problematisch ist und mit Sicherheit am schlimmsten ist.strcmp
es zur Beschreibung des Werts von verwendet wirdwhat_arg
. Das sagt sowieso der relevante Abschnitt aus der C-Norm , auf den sich die Spezifikation von bezieht<cstring>
. Natürlich könnte der Wortlaut klarer sein.Antworten:
Aus dem Dokument :
Dies zeigt, warum Sie Segfault bekommen, die API behandelt es wirklich als echte Zeichenfolge. Wenn in cpp etwas optional war, gibt es im Allgemeinen einen überladenen Konstruktor / eine überladene Funktion, die nicht das übernimmt, was sie nicht benötigt. Das Übergeben
nullptr
einer Funktion, die nicht dokumentiert, dass etwas optional ist, ist also ein undefiniertes Verhalten. Normalerweise nehmen APIs keine Zeiger mit Ausnahme von C-Strings. IMHO ist es also sicher anzunehmen, dass die Übergabe von nullptr für eine Funktion, die a erwartet, einconst char *
undefiniertes Verhalten sein wird. Neuere APIs bevorzugen möglicherweisestd::string_view
diese Fälle.Aktualisieren:
Normalerweise ist es fair anzunehmen, dass eine C ++ - API einen Zeiger verwendet, um NULL zu akzeptieren. Ein Sonderfall sind jedoch C-Strings. Bis
std::string_view
es keinen besseren Weg gab, sie effizient zu bestehen. Im Allgemeinen sollte für eine API-Annahmeconst char *
angenommen werden, dass es sich um eine gültige C-Zeichenfolge handeln muss. dh ein Zeiger auf eine Folge vonchar
s, die mit einem '\ 0' endet.range_error
könnte validieren, dass der Zeiger nicht ist,nullptr
aber er kann nicht validieren, wenn er mit '\ 0' endet. Es ist also besser, keine Validierung durchzuführen.Ich kenne den genauen Wortlaut der Norm nicht, aber diese Voraussetzung wird wahrscheinlich automatisch angenommen.
quelle
Dies geht zurück auf die grundlegende Frage: Ist es in Ordnung, einen std :: string aus nullptr zu erstellen? und was soll es tun ist es?
www.cplusplus.com sagt
Also wann
heißt die Implementierung versucht so etwas zu machen
das ist undefiniert. Was ich als Fehler betrachten würde (ohne den tatsächlichen Wortlaut im Standard gelesen zu haben). Stattdessen hätten die libery Entwickler so etwas machen können
Aber dann muss der Catcher nach nullptr suchen, sonst
what();
stürzt er dort einfach ab. Dasstd::range_error
sollte also entweder eine leere Zeichenfolge oder "(nullptr)" zuweisen, wie es einige andere Sprachen tun.quelle
std::string
und derstd::string
Konstruktor nicht durch Überlastungsauflösung ausgewählt werden sollte.const char *
Überlastung wird durch Überlastungsauflösung ausgewählt