Construire des exceptions standard avec un argument de pointeur nul et des post-conditions impossibles

9

Considérez le programme suivant:

#include<stdexcept>
#include<iostream>

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

GCC et Clang avec libstdc ++ appellent std::terminateet abandonnent le programme avec le message

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

Clang avec libc ++ segfaults sur la construction de l'exception.

Voir godbolt .

Les compilateurs se comportent-ils conformément aux normes? La section pertinente de la norme [diagnostics.range.error] (C ++ 17 N4659) dit qu'il y std::range_errora une const char*surcharge de constructeur qui devrait être préférée à la const std::string&surcharge. La section n'énonce pas non plus de conditions préalables sur le constructeur et indique uniquement la postcondition

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

Cette postcondition a toujours un comportement indéfini s'il what_args'agit d'un pointeur nul, cela signifie-t-il que mon programme a également un comportement indéfini et que les deux compilateurs agissent conformément? Sinon, comment lire ces conditions postérieures impossibles dans la norme?


À la réflexion, je pense que cela doit signifier un comportement indéfini pour mon programme, car si ce n'était pas le cas, des pointeurs (valides) ne pointant pas vers des chaînes terminées par null seraient également autorisés, ce qui n'a clairement aucun sens.

Donc, en supposant que cela soit vrai, je voudrais concentrer davantage la question sur la façon dont la norme implique ce comportement indéfini. Doit-il résulter de l'impossibilité de la postcondition que l'appel a également un comportement indéfini ou la condition préalable a-t-elle simplement été oubliée?


Inspiré par cette question .

noyer
la source
Cela ressemble à std :: range_error est autorisé à stocker des choses par référence, donc je ne serais pas surpris. Appeler what()quand nullptrest passé causerait probablement des problèmes.
Chipster
@Chipster Je ne sais pas exactement ce que vous voulez dire, mais après y avoir repensé, je pense que ce doit être un comportement indéfini. J'ai modifié ma question pour la concentrer davantage sur la façon dont la formulation standard implique un comportement indéfini.
noyer
Si nullptrest passé, je pense qu'il what()faudrait le déréférencer à un moment donné pour obtenir la valeur. Ce serait déréférencer un nullptr, ce qui est au mieux problématique et certain de planter est pire.
Chipster
Je suis d'accord cependant. Ce doit être un comportement non défini. Cependant, mettre cela dans une réponse adéquate expliquant pourquoi est au-delà de mes compétences.
Chipster
Je pense qu'il est prévu que c'est une condition préalable que l'argument pointe vers une chaîne C valide, car il strcmpest utilisé pour décrire la valeur de what_arg. C'est ce que dit de toute façon la section pertinente de la norme C , à laquelle fait référence la spécification de <cstring>. Bien sûr, le libellé pourrait être plus clair.
LF

Réponses:

0

Du doc :

Étant donné que la copie de std :: range_error n'est pas autorisée à lever des exceptions, ce message est généralement stocké en interne sous la forme d'une chaîne comptée par référence allouée séparément. C'est aussi pourquoi il n'y a pas de constructeur prenant std :: string &&: il devrait quand même copier le contenu.

Cela montre pourquoi vous obtenez un défaut de segmentation, l'API le traite vraiment comme une vraie chaîne. En général, dans cpp, si quelque chose était facultatif, il y aura un constructeur / fonction surchargé qui ne prendra pas ce dont il n'a pas besoin. Donc, passer nullptrpour une fonction qui ne documente pas quelque chose comme facultatif va être un comportement non défini. Habituellement, les API ne prennent pas de pointeurs à l'exception des chaînes C. Donc, à mon humble avis, il est sûr de supposer que passer nullptr pour une fonction qui attend a const char *, va être un comportement non défini. Les API plus récentes peuvent préférer std::string_viewpour ces cas.

Mise à jour:

Habituellement, il est juste de supposer qu'une API C ++ prend un pointeur pour accepter NULL. Cependant, les chaînes C sont un cas particulier. Jusqu'à ce std::string_viewqu'il n'y ait pas de meilleur moyen de les passer efficacement. En général, pour une API acceptant const char *, l'hypothèse devrait être qu'il doit s'agir d'une chaîne C valide. c'est-à-dire un pointeur vers une séquence de chars qui se termine par un '\ 0'.

range_errorpourrait valider que le pointeur ne l'est pas nullptrmais il ne peut pas valider s'il se termine par un '\ 0'. Il vaut donc mieux ne pas faire de validation.

Je ne connais pas le libellé exact de la norme, mais cette condition préalable est probablement supposée automatiquement.

balki
la source
-2

Cela revient à la question de base est-il OK de créer une chaîne std :: à partir de nullptr? et que doit-il faire?

www.cplusplus.com dit

Si s est un pointeur nul, si n == npos, ou si la plage spécifiée par [premier, dernier) n'est pas valide, elle provoque un comportement non défini.

Donc quand

throw std::range_error(nullptr);

est appelé la mise en œuvre essaie de faire quelque chose comme

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

qui n'est pas défini. Ce que je considérerais comme un bug (sans avoir lu le libellé réel de la norme). Au lieu de cela, les développeurs Libery auraient pu faire quelque chose comme

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

Mais alors le receveur doit vérifier nullptr what();ou il va juste planter là. Donc, le std::range_errordevrait attribuer une chaîne vide ou "(nullptr)" comme le font d'autres langues.

Surt
la source
"Cela revient à la question de base est-il OK de créer une chaîne std :: à partir de nullptr?" - Je ne pense pas.
Konrad Rudolph
Je ne pense pas que la norme spécifie où l'exception doit enregistrer un std::stringet le std::stringconstructeur ne doit pas être choisi par résolution de surcharge.
noyer
La const char *surcharge est sélectionnée par résolution de surcharge
MM