Construir exceções padrão com argumento de ponteiro nulo e pós-condições impossíveis

9

Considere o seguinte programa:

#include<stdexcept>
#include<iostream>

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

GCC e Clang com chamada libstdc ++ std::terminatee aborte o programa com a mensagem

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

Clang com libc ++ segfaults na construção da exceção.

Veja Godbolt .

Os compiladores estão se comportando em conformidade com o padrão? A seção relevante do padrão [diagnostics.range.error] (C ++ 17 N4659) diz que std::range_errorpossui uma const char*sobrecarga de construtor que deve ser preferida à const std::string&sobrecarga. A seção também não declara pré-condições no construtor e apenas a pós-condição

Pós-condições : strcmp(what(), what_­arg) == 0.

Essa pós-condição sempre tem um comportamento indefinido se what_argfor um ponteiro nulo; isso significa que meu programa também tem um comportamento indefinido e que os dois compiladores agem em conformidade? Caso contrário, como se deve ler essas pós-condições impossíveis no padrão?


Pensando bem, acho que deve significar um comportamento indefinido para o meu programa, porque, se isso não significasse, ponteiros (válidos) que não apontam para seqüências terminadas em nulo também seriam permitidos, o que claramente não faz sentido.

Então, supondo que isso seja verdade, eu gostaria de focar mais a questão em como o padrão implica esse comportamento indefinido. Resulta da impossibilidade da pós-condição que a chamada também tenha um comportamento indefinido ou a pré-condição foi simplesmente esquecida?


Inspirado por esta pergunta .

noz
fonte
Parece que std :: range_error tem permissão para armazenar coisas por referência, então eu não ficaria surpreso. Chamar what()quando nullptré passado provavelmente causaria problemas.
Chipster 30/03
@ Chhipster Não tenho certeza do que exatamente você quer dizer, mas depois de pensar nisso novamente, acho que deve ser um comportamento indefinido. Eu editei minha pergunta para focar mais em como a redação padrão implica um comportamento indefinido.
noz
Se nullptrfor aprovada, eu acho que what()teria que desreferenciar isso em algum momento para obter o valor. Isso seria desreferenciar a nullptr, o que é problemático na melhor das hipóteses e com certeza travar é o pior.
Chipster 30/03
Eu concordo, no entanto. Deve ser um comportamento indefinido. No entanto, colocar isso em uma resposta adequada, explicando o porquê está além das minhas habilidades.
Chipster 30/03
Eu acho que se pretende que seja uma pré-condição que o argumento aponte para uma string C válida, pois strcmpé usada para descrever o valor de what_arg. É o que a seção relevante do padrão C diz de qualquer maneira, a que se refere a especificação de <cstring>. Claro que o texto poderia ser mais claro.
LF

Respostas:

0

Do documento :

Como copiar std :: range_error não tem permissão para gerar exceções, essa mensagem geralmente é armazenada internamente como uma seqüência de caracteres contada por referência alocada separadamente. É também por isso que não há construtor usando std :: string &&: ele teria que copiar o conteúdo de qualquer maneira.

Isso mostra por que você obtém o segfault, a API realmente o trata como uma string real. Em geral no cpp, se algo for opcional, haverá um construtor / função sobrecarregado que não aceita o que não é necessário. Portanto, passar nullptrpara uma função que não documenta algo opcional é um comportamento indefinido. Normalmente, as APIs não usam ponteiros, com exceção das seqüências C. Portanto, no IMHO, é seguro assumir que passar nullptr para uma função que espera const char *a será um comportamento indefinido. APIs mais recentes podem preferir std::string_viewpara esses casos.

Atualizar:

Geralmente, é justo supor que uma API C ++ use um ponteiro para aceitar NULL. No entanto, as strings C são um caso especial. Até que std::string_viewnão havia melhor maneira de passar com eficiência. Em geral, para uma API aceitar const char *, deve-se supor que ela deve ser uma cadeia C válida. ou seja, um ponteiro para uma sequência de chars que termina com um '\ 0'.

range_errorpode validar que o ponteiro não é, nullptrmas não pode validar se terminar com um '\ 0'. Portanto, é melhor não fazer nenhuma validação.

Não sei o texto exato do padrão, mas essa pré-condição provavelmente é assumida automaticamente.

balki
fonte
-2

Isso remonta à pergunta básica. Não há problema em criar um std :: string a partir do nullptr? e o que deveria fazer?

www.cplusplus.com diz

Se s for um ponteiro nulo, se n == npos ou se o intervalo especificado por [first, last) não for válido, isso causará um comportamento indefinido.

Então quando

throw std::range_error(nullptr);

é chamado a implementação tenta fazer algo como

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

que é indefinido. O que eu consideraria um bug (sem ter lido a redação real no padrão). Em vez disso, os desenvolvedores da biblioteca poderiam ter criado algo como

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

Mas o apanhador precisa procurar o nullptr what();ou ele simplesmente trava ali. Portanto, std::range_errordeve-se atribuir uma string vazia ou "(nullptr)", como alguns outros idiomas.

Surt
fonte
"Isso remonta à pergunta básica. Não há problema em criar uma std :: string a partir do nullptr?" Acho que não.
Konrad Rudolph
Eu não acho que o padrão especifique em qualquer lugar que a exceção tenha que salvar um std::stringe o std::stringconstrutor não deve ser escolhido por resolução de sobrecarga.
walnut
A const char *sobrecarga é selecionada por resolução de sobrecarga
MM