Costruisci eccezioni standard con argomento puntatore null e postcondizioni impossibili


9

Considera il seguente programma:

#include<stdexcept>
#include<iostream>

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

GCC e Clang con libstdc ++ chiamano std::terminatee interrompono il programma con il messaggio

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

Clang con libc ++ segfaults alla costruzione dell'eccezione.

Vedi godbolt .

I compilatori si comportano secondo standard? La sezione pertinente dello standard [diagnostics.range.error] (C ++ 17 N4659) afferma che std::range_errorha un const char*sovraccarico del costruttore che dovrebbe essere preferito rispetto al const std::string&sovraccarico. La sezione inoltre non indica alcuna condizione preliminare per il costruttore e indica solo la postcondizione

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

Questa postcondizione ha sempre un comportamento indefinito se what_argè un puntatore nullo, quindi questo significa che anche il mio programma ha un comportamento indefinito e che entrambi i compilatori agiscono in modo conforme? In caso contrario, come si dovrebbero leggere tali postcondizioni impossibili nello standard?


A pensarci bene, penso che debba significare un comportamento indefinito per il mio programma, perché se non lo facesse allora anche i puntatori (validi) che non puntano a stringhe con terminazione nulla sarebbero ammessi, il che chiaramente non ha senso.

Quindi, supponendo che sia vero, vorrei focalizzare maggiormente la domanda su come lo standard implichi questo comportamento indefinito. Dall'impossibilità della postcondizione deriva che anche la chiamata ha un comportamento indefinito o la precondizione è stata semplicemente dimenticata?


Ispirato da questa domanda .


Sembra che std :: range_error sia autorizzato a memorizzare le cose per riferimento, quindi non sarei sorpreso. Chiamare what()quando nullptrviene passato probabilmente causerebbe problemi.
Chipster

@Chipster Non sono sicuro di cosa significhi esattamente, ma dopo averci pensato di nuovo, penso che debba essere un comportamento indefinito. Ho modificato la mia domanda per concentrarmi maggiormente su come la formulazione standard implica un comportamento indefinito.
noce

Se nullptrviene passato, penso che what()dovrebbe dereferenziarlo ad un certo punto per ottenere il valore. Questo sarebbe il dereferenziamento di a nullptr, che è problematico nella migliore delle ipotesi e sicuramente il crash è il peggiore.
Chipster

Sono d'accordo, però. Deve essere un comportamento indefinito. Tuttavia, inserendolo in una risposta adeguata spiegando perché va oltre le mie capacità.
Chipster

Penso che sia inteso che è un prerequisito che l'argomento punti a una stringa C valida, poiché strcmpviene usato per descrivere il valore di what_arg. Questo è ciò che dice comunque la sezione pertinente dello standard C , a cui fa riferimento la specifica di <cstring>. Naturalmente la formulazione potrebbe essere più chiara.
LF

Risposte:


0

Dal documento :

Poiché la copia di std :: range_error non è autorizzata a generare eccezioni, questo messaggio viene in genere archiviato internamente come stringa conteggio dei riferimenti allocata separatamente. Questo è anche il motivo per cui nessun costruttore prende std :: string &&: dovrebbe comunque copiare il contenuto.

Questo dimostra perché diventi segfault, l'API lo tratta davvero come una vera stringa. In generale in cpp se qualcosa fosse facoltativo, ci sarà un costruttore / funzione sovraccarico che non accetta ciò di cui non ha bisogno. Quindi passare nullptrper una funzione che non documenta qualcosa di facoltativo sarà un comportamento indefinito. Di solito le API non accettano i puntatori, ad eccezione delle stringhe C. Quindi, IMHO è sicuro supporre che passare nullptr per una funzione che si aspetta un const char *, sia un comportamento indefinito. Le API più recenti potrebbero preferire std::string_viewper questi casi.

Aggiornare:

Di solito è giusto supporre che un'API C ++ prenda un puntatore per accettare NULL. Tuttavia, le stringhe C sono un caso speciale. Fino a quando std::string_viewnon c'era modo migliore per passarli in modo efficiente. In generale per un'accettazione dell'API const char *, si suppone che debba essere una stringa C valida. cioè un puntatore a una sequenza di chars che termina con un '\ 0'.

range_errorpotrebbe validare che il puntatore non lo è nullptrma non può validare se termina con uno '\ 0'. Quindi è meglio non effettuare alcuna convalida.

Non conosco la formulazione esatta nello standard, ma questa pre-condizione è probabilmente assunta automaticamente.


-2

Questo torna alla domanda di base è OK per creare uno std :: string da nullptr? e cosa dovrebbe fare?

www.cplusplus.com dice

Se s è un puntatore nullo, se n == npos o se l'intervallo specificato da [primo, ultimo) non è valido, provoca un comportamento indefinito.

Cosi quando

throw std::range_error(nullptr);

si chiama l'implementazione cerca di creare qualcosa di simile

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

che non è definito. Che considererei un bug (senza aver letto la formulazione effettiva nello standard). Invece gli sviluppatori di libery avrebbero potuto creare qualcosa di simile

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

Ma poi il ricevitore deve controllare nullptr what();o si bloccherà lì. Quindi std::range_errordovrebbe assegnare una stringa vuota o "(nullptr)" come fanno alcune altre lingue.


"Questo torna alla domanda di base è OK per creare uno std :: string da nullptr?" - Non credo.
Konrad Rudolph,

Non credo che lo standard specifichi da nessuna parte che l'eccezione deve salvare un std::stringe il std::stringcostruttore non dovrebbe essere scelto dalla risoluzione del sovraccarico.
noce,

Il const char *sovraccarico viene selezionato dalla risoluzione del sovraccarico
MM
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.