Konstruieren Sie Standardausnahmen mit Nullzeigerargumenten und unmöglichen Nachbedingungen

9

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::terminatedas 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_erroreine 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_arges 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 .

Nussbaum
quelle
Es hört sich so an, als ob std :: range_error Dinge als Referenz speichern darf, also wäre ich nicht überrascht. Ein Anruf, what()wenn er nullptrbestanden wird, würde wahrscheinlich Probleme verursachen.
Chipster
@Chipster Ich bin mir nicht sicher, was genau du meinst, aber nachdem ich noch einmal darüber nachgedacht habe, denke ich, dass es undefiniertes Verhalten sein muss. Ich habe meine Frage bearbeitet, um mich mehr darauf zu konzentrieren, wie der Standardtext undefiniertes Verhalten impliziert.
Walnuss
Wenn nullptrbestanden wird, würde ich denken, dass es what()irgendwann dereferenzieren müsste, um den Wert zu erhalten. Das wäre eine Dereferenzierung von a nullptr, die bestenfalls problematisch ist und mit Sicherheit am schlimmsten ist.
Chipster
Ich stimme jedoch zu. Es muss undefiniertes Verhalten sein. Dies in eine angemessene Antwort zu setzen, um zu erklären, warum dies meine Fähigkeiten übersteigt.
Chipster
Ich denke, es ist beabsichtigt, dass es eine Voraussetzung ist, dass das Argument auf eine gültige C-Zeichenfolge verweist, da strcmpes zur Beschreibung des Werts von verwendet wird what_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.
LF

Antworten:

0

Aus dem Dokument :

Da das Kopieren von std :: range_error keine Ausnahmen auslösen darf, wird diese Nachricht normalerweise intern als separat zugewiesene Zeichenfolge mit Referenzzählung gespeichert. Dies ist auch der Grund, warum es keinen Konstruktor gibt, der std :: string && verwendet: Er müsste den Inhalt trotzdem kopieren.

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 nullptreiner 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, ein const char *undefiniertes Verhalten sein wird. Neuere APIs bevorzugen möglicherweise std::string_viewdiese 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_viewes keinen besseren Weg gab, sie effizient zu bestehen. Im Allgemeinen sollte für eine API-Annahme const char *angenommen werden, dass es sich um eine gültige C-Zeichenfolge handeln muss. dh ein Zeiger auf eine Folge von chars, die mit einem '\ 0' endet.

range_errorkönnte validieren, dass der Zeiger nicht ist, nullptraber 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.

balki
quelle
-2

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

Wenn s ein Nullzeiger ist, wenn n == npos ist oder wenn der durch [first, last] angegebene Bereich nicht gültig ist, führt dies zu undefiniertem Verhalten.

Also wann

throw std::range_error(nullptr);

heißt die Implementierung versucht so etwas zu machen

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

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

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

Aber dann muss der Catcher nach nullptr suchen, sonst what();stürzt er dort einfach ab. Das std::range_errorsollte also entweder eine leere Zeichenfolge oder "(nullptr)" zuweisen, wie es einige andere Sprachen tun.

Surt
quelle
"Dies geht zurück auf die grundlegende Frage: Ist es in Ordnung, einen std :: string aus nullptr zu erstellen?" - Ich glaube nicht.
Konrad Rudolph
Ich denke nicht, dass der Standard irgendwo angibt, dass die Ausnahme a speichern muss std::stringund der std::stringKonstruktor nicht durch Überlastungsauflösung ausgewählt werden sollte.
Walnuss
Die const char *Überlastung wird durch Überlastungsauflösung ausgewählt
MM