Konstruuj standardowe wyjątki za pomocą argumentu zerowego wskaźnika i niemożliwych warunków dodatkowych

9

Rozważ następujący program:

#include<stdexcept>
#include<iostream>

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

GCC i Clang z wywołaniem libstdc ++ std::terminatei przerywają program z komunikatem

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

Clang z libc ++ segfaults przy budowie wyjątku.

Zobacz godbolt .

Czy kompilatory zachowują się zgodnie ze standardami? Odpowiednia sekcja standardu [diagnostics.range.error] (C ++ 17 N4659) mówi, że std::range_errorma const char*przeciążenie konstruktora, które powinno być lepsze niż const std::string&przeciążenie. Sekcja nie zawiera również żadnych warunków wstępnych dotyczących konstruktora i podaje jedynie warunek końcowy

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

Ten warunek zawsze ma niezdefiniowane zachowanie, jeśli what_argjest wskaźnikiem zerowym, więc czy to oznacza, że ​​mój program ma również niezdefiniowane zachowanie i że oba kompilatory działają zgodnie? Jeśli nie, to jak należy odczytać takie niemożliwe warunki w normie?


Po zastanowieniu, myślę, że musi to oznaczać niezdefiniowane zachowanie mojego programu, ponieważ jeśli nie, to (prawidłowe) wskaźniki dozwolone w łańcuchach zakończonych znakiem zerowym również byłyby dozwolone, co oczywiście nie ma sensu.

Zakładając, że to prawda, chciałbym bardziej skoncentrować pytanie na tym, jak standard implikuje to niezdefiniowane zachowanie. Czy z niemożności spełnienia warunku wynika, że ​​wezwanie ma również niezdefiniowane zachowanie, czy też warunek ten został po prostu zapomniany?


Zainspirowany tym pytaniem .

orzech włoski
źródło
Wygląda na to, że std :: range_error może przechowywać rzeczy przez odniesienie, więc nie byłbym zaskoczony. Dzwonienie, what()gdy nullptrzostanie przekazane, prawdopodobnie spowoduje problemy.
Chipster
@Chipster Nie jestem pewien, co dokładnie masz na myśli, ale po ponownym przemyśleniu, myślę, że musi to być nieokreślone zachowanie. Zredagowałem swoje pytanie, aby bardziej skupić się na tym, w jaki sposób standardowe sformułowanie implikuje niezdefiniowane zachowanie.
orzech
Gdyby nullptrzostał zaliczony, pomyślałbym, what()że w pewnym momencie musiałbym go odrzucić, aby uzyskać wartość. To byłoby dereferencjonowanie a nullptr, co w najlepszym wypadku jest problematyczne i na pewno jest najgorsze.
Chipster
Zgadzam się jednak. To musi być niezdefiniowane zachowanie. Jednak umieszczenie tego w odpowiedniej odpowiedzi wyjaśniającej, dlaczego przekracza moje umiejętności.
Chipster
Wydaje mi się, że zamierzeniem jest, aby argumentem był prawidłowy ciąg C, ponieważ strcmpjest on używany do opisania wartości what_arg. Tak zresztą mówi odpowiednia sekcja normy C , do której odwołuje się specyfikacja <cstring>. Oczywiście sformułowanie może być jaśniejsze.
LF

Odpowiedzi:

0

Z dokumentu :

Ponieważ kopiowanie std :: range_error nie może generować wyjątków, ten komunikat jest zwykle przechowywany wewnętrznie jako osobno przydzielony ciąg zliczający odwołania. Dlatego też nie ma konstruktora przyjmującego std :: string &&: i tak musiałby skopiować treść.

To pokazuje, dlaczego dostajesz segfault, API naprawdę traktuje to jako prawdziwy ciąg. Ogólnie w cpp, jeśli coś było opcjonalne, pojawi się przeciążony konstruktor / funkcja, która nie pobierze tego, czego nie potrzebuje. Zatem przekazanie nullptrfunkcji, która nie dokumentuje czegoś, co jest opcjonalne, będzie zachowaniem niezdefiniowanym. Zwykle interfejsy API nie przyjmują wskaźników z wyjątkiem ciągów C. Tak więc IMHO można bezpiecznie przyjąć, że przekazanie wartości nullptr dla funkcji, która oczekuje const char *, że zachowanie będzie niezdefiniowane, będzie niemożliwe. W std::string_viewtakich przypadkach preferowane mogą być nowsze interfejsy API .

Aktualizacja:

Zwykle słuszne jest założenie, że interfejs API języka C ++ przyjmuje wskaźnik, aby zaakceptować wartość NULL. Jednak łańcuchy C są szczególnym przypadkiem. Do tego czasu std::string_viewnie było lepszego sposobu na ich skuteczne przekazanie. Zasadniczo dla akceptacji API const char *należy założyć, że musi to być prawidłowy ciąg C. tzn. wskaźnik do sekwencji chars, która kończy się na „\ 0”.

range_errormoże sprawdzić, czy wskaźnik nie jest, nullptrale nie może sprawdzić, czy kończy się na „\ 0”. Więc lepiej nie przeprowadzać żadnej weryfikacji.

Nie znam dokładnego sformułowania w standardzie, ale ten warunek wstępny jest prawdopodobnie zakładany automatycznie.

balki
źródło
-2

Wraca do podstawowego pytania: czy można utworzyć std :: string z nullptr? a co powinien to zrobić?

www.cplusplus.com mówi

Jeśli s jest wskaźnikiem zerowym, jeśli n == npos lub jeśli zakres określony przez [pierwszy, ostatni) jest niepoprawny, powoduje to niezdefiniowane zachowanie.

Więc kiedy

throw std::range_error(nullptr);

nazywa się implementacja próbuje coś zrobić

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

który jest niezdefiniowany. Który uznałbym za błąd (bez przeczytania właściwego sformułowania w standardzie). Zamiast tego programiści libery mogliby stworzyć coś podobnego

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

Ale wtedy łapacz musi sprawdzić, what();czy nie ma nullptr, bo inaczej tam się zawiesi. Dlatego std::range_errornależy przypisać pusty ciąg znaków lub „(nullptr)”, tak jak niektóre inne języki.

Surt
źródło
„Wróćmy do podstawowego pytania, czy można utworzyć std :: string z nullptr?” - Nie sądzę, że tak.
Konrad Rudolph
Nie wydaje mi się, żeby standard nigdzie określał, że wyjątek musi zapisać a, std::stringa std::stringkonstruktor nie powinien być wybierany przez rozwiązanie przeciążenia.
orzech
const char *Przeciążenia jest wybrany rozmiar przeciążenia
MM