Actualmente escribo
assert(false);
en lugares a los que se supone que mi código nunca debe llegar. Un ejemplo, en un estilo muy C-ish, es:
int findzero( int length, int * array ) {
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
assert(false);
}
Mi compilador reconoce que el programa finaliza una vez que se alcanza la afirmación (falso). Sin embargo, cada vez que compilo con -DNDEBUG por motivos de rendimiento, la última afirmación desaparece y el compilador advierte que la ejecución finaliza la función sin una declaración de retorno.
¿Cuáles son mejores alternativas de terminar un programa si se ha llegado a una parte del código supuestamente inalcanzable? La solución debería
- ser reconocido por el compilador y no producir advertencias (como las anteriores u otras)
- tal vez incluso permita un mensaje de error personalizado.
Estoy explícitamente interesado en soluciones sin importar si es C++ moderno o como C de los 90.
Carreras de ligereza en órbita
reemplazando tu assert(false)
es exactamente para lo que son los integrados “inalcanzables”.
Son un equivalente semántico a su uso de assert(false)
. De hecho, VS se escribe de manera muy similar.
CCG/Clang/Intel:
__builtin_unreachable()
MSVS:
__assume(false)
Estos tienen efecto independientemente de NDEBUG
(a diferencia de assert
) o niveles de optimización.
Su compilador, particularmente con los integrados anteriores pero también posiblemente con su assert(false)
asiente con la cabeza entendiendo que eres prometedor esa parte de la función nunca será alcanzada. Puede usar esto para realizar algunas optimizaciones en ciertas rutas de código y silenciará las advertencias sobre devoluciones faltantes porque ya ha prometido que fue deliberado.
La compensación es que la declaración en sí tiene un comportamiento indefinido (muy parecido a avanzar y fluir al final de la función). En algunas situaciones, es posible que desee considerar lanzar una excepción (o devolver algún valor de “código de error” en su lugar), o llamar std::abort()
(en C++) si solo desea finalizar el programa.
Hay una propuesta (P0627R0), para agregar esto a C++ como un atributo estándar.
De los documentos de GCC en Builtins:
Si el flujo de control alcanza el punto de la
__builtin_unreachable
, el programa no está definido. Es útil en situaciones en las que el compilador no puede deducir la inaccesibilidad del código. [..]
-
¿Por qué no llamar?
std::abort()
¿directamente?– Ayxan Haqverdili
12 de septiembre de 2019 a las 14:43
-
@Ayxan porque
std::abort()
promete un comportamiento específico.__assume(false)
no hace tales promesas– Caleth
12 de septiembre de 2019 a las 14:54
-
@Ayxan Porque
std::abort
es una cosa diferente con un significado diferente y efectos diferentes. Las funciones integradas son para cuando sabes que no se alcanzará una parte del código (a menos que todas las apuestas estén realmente canceladas de todos modos); abortar es para cuando desea capturar un fragmento de código que se está alcanzando.– Carreras de ligereza en órbita
12 de septiembre de 2019 a las 14:56
-
@Ayxan Interesante redacción, sin embargo. Lo haces sonar como si pensaras
std::abort()
se llama indirectamente por estos incorporados?– Carreras de ligereza en órbita
12 de septiembre de 2019 a las 14:57
-
Eso parece venir lo más cerca posible de lo que estoy buscando. El único inconveniente es que técnicamente depende del proveedor, aunque es casi un estándar en diferentes compiladores. Creo que algo así debería estar estandarizado.
– shuhalo
12/09/2019 a las 15:00
Ayxan Haqverdili
Como una solución totalmente portátil, considere esto:
[[ noreturn ]] void unreachable(std::string_view msg = "<No Message>") {
std::cerr << "Unreachable code reached. Message: " << msg << std::endl;
std::abort();
}
La parte del mensaje es, por supuesto, opcional.
-
¿Es seguro no vaciar la transmisión (para que el mensaje se imprima) antes de llamar a abort ()?
– nada
12 sep 2019 a las 15:43
-
@nada
std::cerr
debe enjuagar automáticamente después de cada operación.– Santo Gato Negro
17/09/2019 a las 18:00
-
std::endl también se vacía.
– usuario79878
24 de marzo a las 16:50
-
@ user79878 originalmente tenía ‘\n’ en lugar de endl y lo cambié en respuesta al comentario.
– Ayxan Haqverdili
24 de marzo a las 17:30
frerich raabe
me gusta usar
assert(!"This should never happen.");
…que también se puede usar con una condición, como en
assert(!vector.empty() || !"Cannot take element from empty container." );
Lo bueno de esto es que la cadena aparece en el mensaje de error en caso de que una afirmación no se cumpla.
-
¿Se puede envolver de forma segura (más o menos segura) la segunda línea en una macro que toma dos argumentos?
– shuhalo
17 sep 2019 a las 20:09
-
Sí, pero no soluciona tu problema, ya que
assert
en sí mismo es una macro que se vuelve nula paraNDEBUG
construye independientemente.– bicho
23 de marzo de 2021 a las 6:15
-
@bug Sí, tienes razón, creo que mi respuesta debería haber sido un comentario. Quería proponer una ligera mejora sobre
assert(false);
que satisface el “permitir un mensaje de error personalizado”. requisito.– Frerich Raabe
23 de marzo de 2021 a las 8:55
Parece que std::unreachable() llegó a C++23:
PSkocik
Uso una aserción personalizada que se convierte en __builtin_unreachable()
o *(char*)0=0
cuando NDEBUG
está activado (también uso una variable de enumeración en lugar de una macro para poder configurar fácilmente NDEBUG por ámbito).
En pseudocódigo, es algo como:
#define my_assert(X) do{ \
if(!(X)){ \
if (my_ndebug) MY_UNREACHABLE(); \
else my_assert_fail(__FILE__,__LINE__,#X); \
} \
}while(0)
El __builtin_unreachable()
debería eliminar la advertencia y ayudar con la optimización al mismo tiempo, pero en el modo de depuración, es mejor tener una afirmación o una abort();
allí para que tengas un pánico confiable. (__builtin_unreachable()
solo le da un comportamiento indefinido cuando se alcanza).
-
Creo que deberían incluir ser un estándar
unreachable
macro similar aassert
en el estándar C (o C++).– shuhalo
12 de septiembre de 2019 a las 14:58
-
@shuhalo Según mi respuesta, hay una propuesta para hacer precisamente eso.
– Carreras de ligereza en órbita
12 de septiembre de 2019 a las 14:59
-
@shuhalo Honestamente, odio los comités que inflan innecesariamente mi idioma favorito. Los compiladores podrían simplemente reconocer
*(char*)0=0;
como equivalente a__builtin_unreachable();
porque semánticamente es equivalente, y entonces no hay necesidad de ninguna propuesta o extensión.– PSkocik
12 de septiembre de 2019 a las 15:03
recomiendo Pautas básicas de C++‘s espera y asegura. Se pueden configurar para abortar (predeterminado), lanzar o no hacer nada en caso de infracción.
Para suprimir las advertencias del compilador en ramas inalcanzables, también puede usar GSL_ASSUME
.
#include <gsl/gsl>
int findzero( int length, int * array ) {
Expects(length >= 0);
Expects(array != nullptr);
for( int i = 0; i < length; i++ )
if( array[i] == 0 )
return i;
Expects(false);
// or
// GSL_ASSUME(false);
}
-
Creo que deberían incluir ser un estándar
unreachable
macro similar aassert
en el estándar C (o C++).– shuhalo
12 de septiembre de 2019 a las 14:58
-
@shuhalo Según mi respuesta, hay una propuesta para hacer precisamente eso.
– Carreras de ligereza en órbita
12 de septiembre de 2019 a las 14:59
-
@shuhalo Honestamente, odio los comités que inflan innecesariamente mi idioma favorito. Los compiladores podrían simplemente reconocer
*(char*)0=0;
como equivalente a__builtin_unreachable();
porque semánticamente es equivalente, y entonces no hay necesidad de ninguna propuesta o extensión.– PSkocik
12 de septiembre de 2019 a las 15:03
gibones cristianos
assert
está destinado a escenarios que REALMENTE se supone que es imposible que sucedan durante la ejecución. Es útil en la depuración señalar “Oye, resulta que lo que pensabas que era imposible, de hecho, no es imposible”. se parece a lo que tu debería estar haciendo en el ejemplo dado está expresando la falla de la función, tal vez devolviendo -1
ya que eso no sería un índice válido. En algunos casos, puede ser útil establecer errno
para aclarar la naturaleza exacta de un error. Luego, con esta información, la función de llamada puede decidir cómo manejar dicho error.
Dependiendo de cuán crítico sea este error para el resto de la aplicación, puede intentar recuperarse o simplemente registrar el error y llamar exit
para sacarlo de su miseria.
¿Lanzar una excepción?
– usuario2100815
12 de septiembre de 2019 a las 14:33
abort()
? Aunque no estoy seguro si entendí bien la pregunta– Ayxan Haqverdili
12 de septiembre de 2019 a las 14:39
¿Cómo debería comportarse su código si
0
realmente no se encuentra en la matriz? estas asumiendo que0
hay alguna parte como se garantiza eso??– abelenki
12 de septiembre de 2019 a las 14:47
“Mejores alternativas para afirmar (falso) en C / C ++” ¿Qué tal agregar el manejo de errores real en el código de producción …
– Lundin
12 de septiembre de 2019 a las 14:52
Nunca dejaría que el código “inalcanzable” pasara una revisión de código. Es un defecto de diseño. La persona que llama sabe lo que
array
tiendas, y él es responsable de afirmar lo imposible. Dejarfindzero
devolver un opcional y afirmar en el sitio de la llamada.– local-ninja
12 sep 2019 a las 15:15