Mejores alternativas para afirmar (falso) en C/C++

8 minutos de lectura

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.

  • ¿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 que 0 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. Dejar findzero devolver un opcional y afirmar en el sitio de la llamada.

    – local-ninja

    12 sep 2019 a las 15:15

Lightness Races en el avatar de usuario de Orbit
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

Avatar de usuario de Ayxan Haqverdili
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

Avatar de usuario de Frerich Raabe
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 para NDEBUG 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:

https://en.cppreference.com/w/cpp/utility/unreachable

Avatar de usuario de PSkocik
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 a assert 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 a assert 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

Avatar de usuario de Christian Gibbons
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.

¿Ha sido útil esta solución?