Construya excepciones estándar con argumento de puntero nulo y postcondiciones imposibles

9

Considere el siguiente programa:

#include<stdexcept>
#include<iostream>

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

GCC y Clang con libstdc ++ llaman std::terminatey cancelan el programa con el mensaje

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

Clang con libc ++ segfaults en la construcción de la excepción.

Ver Godbolt .

¿Los compiladores se comportan conforme a la norma? La sección relevante del estándar [diagnostics.range.error] (C ++ 17 N4659) dice que std::range_errortiene una const char*sobrecarga del constructor que debería preferirse a la const std::string&sobrecarga. La sección tampoco establece condiciones previas en el constructor y solo establece la condición posterior

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

Esta condición posterior siempre tiene un comportamiento indefinido si what_arges un puntero nulo, entonces, ¿significa esto que mi programa también tiene un comportamiento indefinido y que ambos compiladores actúan conforme? Si no es así, ¿cómo debería uno leer tales postcondiciones imposibles en el estándar?


Pensándolo bien, creo que debe significar un comportamiento indefinido para mi programa, porque si no fuera así, los punteros (válidos) que no apuntan a cadenas terminadas en nulo también estarían permitidos, lo que claramente no tiene sentido.

Entonces, suponiendo que eso sea cierto, me gustaría centrar la pregunta más en cómo el estándar implica este comportamiento indefinido. ¿Se deduce de la imposibilidad de la condición posterior que la llamada también tiene un comportamiento indefinido o simplemente se olvidó la condición previa?


Inspirado por esta pregunta .

nuez
fuente
Parece que std :: range_error puede almacenar cosas por referencia, por lo que no me sorprendería. Llamar what()cuando nullptrse pasa probablemente causaría problemas.
Chipster
@Chipster No estoy seguro de lo que quieres decir exactamente, pero después de pensar en esto nuevamente, creo que debe ser un comportamiento indefinido. He editado mi pregunta para centrarme más en cómo la redacción estándar implica un comportamiento indefinido.
nogal
Si nullptrse aprueba, creo que what()tendría que desreferenciarlo en algún momento para obtener el valor. Eso sería desreferenciar a nullptr, lo cual es problemático en el mejor de los casos y es seguro que colapsar sea peor.
Chipster
Sin embargo, estoy de acuerdo. Debe ser un comportamiento indefinido. Sin embargo, poner eso en una respuesta adecuada explicando por qué está más allá de mis habilidades.
Chipster
Creo que se pretende que sea una condición previa que el argumento apunte a una cadena C válida, ya que strcmpse usa para describir el valor de what_arg. Eso es lo que dice la sección relevante del estándar C de todos modos, a lo que se refiere la especificación de <cstring>. Por supuesto, la redacción podría ser más clara.
LF

Respuestas:

0

Del documento :

Debido a que copiar std :: range_error no tiene permitido generar excepciones, este mensaje generalmente se almacena internamente como una cadena contada por referencia asignada por separado. Esta es también la razón por la cual no hay ningún constructor que tome std :: string &&: de todos modos, tendría que copiar el contenido.

Esto muestra por qué obtienes segfault, la API realmente lo trata como una cadena real. En general, en cpp, si algo era opcional, habrá un constructor / función sobrecargado que no toma lo que no necesita. Entonces, pasar nullptrpor una función que no documenta que algo sea opcional será un comportamiento indefinido. Por lo general, las API no toman punteros con la excepción de las cadenas C. Entonces, en mi humilde opinión, es seguro asumir que pasar nullptr para una función que espera un const char *comportamiento va a ser indefinido. Las API más nuevas pueden preferir std::string_viewpara esos casos.

Actualizar:

Por lo general, es justo asumir que una API de C ++ toma un puntero para aceptar NULL. Sin embargo, las cuerdas C son un caso especial. Hasta que std::string_viewno hubo mejor manera de pasarlos de manera eficiente. En general, para una aceptación de API const char *, se debe suponer que tiene que ser una cadena C válida. es decir, un puntero a una secuencia de chars que termina con un '\ 0'.

range_errorpodría validar que el puntero no lo es, nullptrpero no puede validar si termina con un '\ 0'. Por lo tanto, es mejor no hacer ninguna validación.

No sé la redacción exacta en el estándar, pero esta condición previa probablemente se asume automáticamente.

balki
fuente
-2

Esto vuelve a la pregunta básica: ¿está bien crear un std :: string desde nullptr? y que debe hacer es?

www.cplusplus.com dice

Si s es un puntero nulo, si n == npos, o si el rango especificado por [first, last) no es válido, provoca un comportamiento indefinido.

Así que cuando

throw std::range_error(nullptr);

se llama la implementación intenta hacer algo como

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

que es indefinido Lo que consideraría un error (sin haber leído la redacción real en el estándar). En cambio, los desarrolladores de la biblioteca podrían haber hecho algo como

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

Pero entonces el receptor tiene que comprobar si hay nullptr in what();o simplemente se bloqueará allí. Por lo tanto, std::range_errordebe asignar una cadena vacía o "(nullptr)" como lo hacen otros idiomas.

Surt
fuente
"Esto vuelve a la pregunta básica: ¿está bien crear un std :: string desde nullptr?" - No creo que lo haga.
Konrad Rudolph
No creo que el estándar especifique en ningún lugar que la excepción tenga que guardar ay std::stringel std::stringconstructor no debería elegirse por resolución de sobrecarga.
nogal
La const char *sobrecarga se selecciona por resolución de sobrecarga
MM