valores tipos retornan que programacion parametros funciones funcion ejemplos ejemplo con c++ c optimization language-lawyer undefined-behavior

c++ - retornan - tipos de funciones en c



¿Se supone que todas las funciones en C/C++ regresan? (7)

¿Es el compilador libre de asumir que una función siempre regresará?

No es legal en C o C ++ que un compilador optimice sobre esa base, a menos que de alguna manera sepa específicamente que el ereport devuelve (por ejemplo, insertándolo e inspeccionando el código).

ereport depende de al menos una #define ereport y de los valores pasados, por lo que no puedo estar seguro, pero ciertamente parece estar diseñado para no regresar (y llama a una función externa errstart que, hasta donde sabe el compilador , puede o no puede volver). Entonces, si el compilador realmente asume que siempre regresa, o bien el compilador es incorrecto, o la implementación de ereport es incorrecta, o lo he malinterpretado por completo.

El papel dice:

Sin embargo, el programador no informó al compilador que la llamada a ereport (ERROR, :::) no vuelve.

No creo que el programador tenga tal obligación, a menos que tal vez haya alguna extensión no estándar vigente al compilar este código en particular, que permita una optimización documentada para romper el código válido en ciertas condiciones.

Desafortunadamente, es bastante difícil probar que la transformación del código es incorrecta citando el estándar, ya que no puedo citar nada para mostrar que no hay, escondido en algún lugar de las páginas 700-900, una pequeña cláusula que dice "oh, por El camino, todas las funciones deben volver ". No he leído todas las líneas del estándar, pero tal cláusula sería absurda: las funciones deben poder llamar a abort() o exit() o longjmp() . En C ++ también pueden lanzar excepciones. Y se les debe permitir hacer esto condicionalmente: el atributo noreturn significa que la función nunca regresa, no que podría no regresar, y su ausencia no prueba nada sobre si la función regresa o no. Mi experiencia de ambos estándares es que no son (que) absurdos.

Las optimizaciones no pueden romper programas válidos, están limitadas por la regla "como si" de que se conserva el comportamiento observable. Si ereport no regresa, entonces la "optimización" cambia el comportamiento observable del programa (de hacer lo que hace ereport lugar de regresar, a tener un comportamiento indefinido debido a la división por cero). Por lo tanto, está prohibido.

Hay más información sobre este tema en particular aquí:

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=616180

Menciona un informe de error de GCC http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29968 que fue (con razón IMO) rechazado, pero si ereport no vuelve, el problema de PostGreSQL no es el mismo. informe de error de GCC rechazado.

En la descripción del error de Debian está el siguiente:

Los chicos de gcc están llenos de eso. El problema que es relevante aquí es la definición de puntos de secuencia del estándar C y, en particular, el requisito de que los efectos secundarios visibles de una declaración posterior no puedan ocurrir antes de la ejecución de una llamada de función anterior. La última vez que los molesté acerca de esto, obtuve una leve afirmación de que un SIGFPE no era un efecto secundario dentro de las definiciones de la especificación. En ese punto, la discusión útil se detuvo, porque es imposible negociar con alguien que esté dispuesto a reclamar eso.

De hecho, si una declaración posterior tiene UB, se establece explícitamente en el estándar que todo el programa tiene UB. Ben tiene la cita en su respuesta. No es el caso (como parece pensar esta persona) que todos los efectos secundarios visibles deben ocurrir hasta el último punto de secuencia antes de la UB. UB permite inventar una máquina del tiempo (y más prosaicamente, permite una ejecución fuera de orden que asume que todo lo ejecutado tiene un comportamiento definido). Los chicos de gcc no están llenos de eso si eso es todo lo que dicen.

Un SIGFPE sería un efecto secundario visible si el compilador decide garantizar y documentar (como una extensión de la norma) que ocurre, pero si es solo el resultado de UB, entonces no lo es. Compare, por ejemplo, la opción -fwrapv con GCC, que cambia el desbordamiento de enteros de UB (lo que dice el estándar) a envolvente (lo que garantiza el compilador, solo si especifica la opción ). En MIPS, gcc tiene una opción -mcheck-zero-division , que parece que define el comportamiento en la división por cero, pero nunca lo he usado.

Es posible que los autores del artículo notaran lo incorrecto de esa queja contra GCC, y el pensamiento de que uno de los autores de PostGreSQL estaba equivocado de esta manera los influenció cuando pusieron las citas de snigger en:

Encontramos siete problemas similares en PostgreSQL, que fueron anotados como "errores GCC" en los comentarios del código fuente

Pero una función que no regresa es muy diferente de una función que regresa después de algunos efectos secundarios. Si no vuelve, la declaración que tendría UB no se ejecuta dentro de la definición de la máquina abstracta C (o C ++) en el estándar. Las declaraciones no alcanzadas no se ejecutan: espero que esto no sea discutible. Entonces, si los "chicos de gcc" reclamaran que UB a partir de declaraciones no alcanzadas deja todo el programa indefinido, entonces estarían llenos de eso. No sé que hayan afirmado eso, y al final del informe Debian hay una sugerencia de que el problema podría haber desaparecido en GCC 4.4. Si es así, es posible que PostGreSQL haya encontrado un error finalmente reconocido, no (como el autor del artículo que usted vincula cree) una optimización válida o (como la persona que dice que los chicos de gcc están llenos de eso cree) una mala interpretación del Estándar por los autores de GCC.

Estaba leyendo este documento sobre comportamiento indefinido y uno de los ejemplos de "optimizaciones" parece altamente dudoso

if (arg2 == 0) ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); /* No overflow is possible */ PG_RETURN_INT32((int32) arg1 / arg2);

Figura 2 : Una optimización inesperada anula la verificación de división por cero, en src/backend/utils/adt/int8.c de PostgreSQL. La llamada al ereport(ERROR, :::) generará una excepción.

Esencialmente, el compilador asume que el ereport regresará, y elimina la verificación arg2 == 0 , ya que la presencia de la división implica un denominador distinto de cero, es decir, arg2 != 0 .

¿Es esta una optimización válida? ¿Es el compilador libre de asumir que una función siempre regresará?

EDITAR: Todo depende de un ereport , que se describe así:

84 /*---------- 85 * New-style error reporting API: to be used in this way: 86 * ereport(ERROR, 87 * (errcode(ERRCODE_UNDEFINED_CURSOR), 88 * errmsg("portal /"%s/" not found", stmt->portalname), 89 * ... other errxxx() fields as needed ...)); 90 * 91 * The error level is required, and so is a primary error message (errmsg 92 * or errmsg_internal). All else is optional. errcode() defaults to 93 * ERRCODE_INTERNAL_ERROR if elevel is ERROR or more, ERRCODE_WARNING 94 * if elevel is WARNING, or ERRCODE_SUCCESSFUL_COMPLETION if elevel is 95 * NOTICE or below. 96 * 97 * ereport_domain() allows a message domain to be specified, for modules that 98 * wish to use a different message catalog from the backend''s. To avoid having 99 * one copy of the default text domain per .o file, we define it as NULL here 100 * and have errstart insert the default text domain. Modules can either use 101 * ereport_domain() directly, or preferably they can override the TEXTDOMAIN 102 * macro. 103 * 104 * If elevel >= ERROR, the call will not return; we try to inform the compiler 105 * of that via pg_unreachable(). However, no useful optimization effect is 106 * obtained unless the compiler sees elevel as a compile-time constant, else 107 * we''re just adding code bloat. So, if __builtin_constant_p is available, 108 * use that to cause the second if() to vanish completely for non-constant 109 * cases. We avoid using a local variable because it''s not necessary and 110 * prevents gcc from making the unreachability deduction at optlevel -O0. 111 *----------


Creo que la respuesta se encuentra, al menos para C ++, en la sección 1.9p5

Una implementación conforme que ejecuta un programa bien formado producirá el mismo comportamiento observable que una de las ejecuciones posibles de la instancia correspondiente de la máquina abstracta con el mismo programa y la misma entrada. Sin embargo, si alguna de esas ejecuciones contiene una operación no definida, esta Norma Internacional no impone ningún requisito a la implementación que ejecuta ese programa con esa entrada ( ni siquiera con respecto a las operaciones que preceden a la primera operación no definida ).

De hecho, la macro se expande a una llamada a errstart que devolverá (ERROR >= ERROR) , obviamente verdadero. Eso activa una llamada a errfinish que llama a proc_exit que ejecuta una limpieza registrada y luego la exit función de tiempo de ejecución estándar. Por lo tanto, no hay ninguna ejecución posible que contenga una división por cero. Sin embargo, la lógica del compilador que prueba esto debe haber salido mal. O tal vez una versión anterior del código no pudo salir correctamente.


El documento no dice que se haya eliminado la comprobación if (arg2 == 0) . Dice que la división se mueve antes del cheque .

Citando el papel :

... GCC mueve la división antes de la verificación cero arg2 == 0 , causando la división por cero.

El resultado es el mismo, pero el razonamiento es diferente.

Si el compilador cree que el ereport regresará, entonces "sabe" que la división se realizará en todos los casos. Además, la sentencia if no afecta los argumentos de la división. Y obviamente, la división no afecta a la sentencia if. Y mientras que la llamada al ereport puede tener efectos secundarios observables, la división no lo hace (si ignoramos cualquier excepción de división por cero).

Por lo tanto, el compilador cree que la regla as-if le da la libertad de reordenar estas afirmaciones entre sí: puede mover la división antes de la prueba porque el comportamiento observable debe ser idéntico (para todos los casos que producen un comportamiento definido). ).

Una forma de verlo es que el comportamiento indefinido incluye el viaje en el tiempo. ;-)

Yo diría que el comportamiento indefinido (por ejemplo, dividir por 0), debe considerarse un comportamiento observable. Eso evitaría este reordenamiento porque el comportamiento observable de la división debe ocurrir después del comportamiento observable de la llamada al ereport . Pero no escribo estándares ni compiladores.


En los sistemas embebidos, las funciones que nunca regresan son comunes. Tampoco deberían ser optimizados.

Por ejemplo, un algoritmo común es tener un bucle para siempre en main() (también conocido como el bucle de fondo), y toda la funcionalidad tiene lugar en una ISR (Rutina de servicio de interrupción).

Otro ejemplo son las tareas RTOS. En nuestro proyecto de sistema integrado, tenemos tareas que están en un bucle de infinte: Colgar en la cola de mensajes, procesar el mensaje, repetir. Lo harán durante la vida del proyecto.

Algunos sistemas integrados tienen bucles de apagado seguro donde colocan la máquina en un estado seguro, bloqueando todas las entradas del usuario y esperan a que se apague o reinicie la alimentación.

Además, algunos sistemas embebidos pueden apagar el sistema. Apagar la alimentación evita que el sistema regrese.

Hay razones por las que no es necesario devolver todas las funciones o es necesario que se las devuelva. Si regresaran todas las funciones que están en su teléfono celular, no sería lo suficientemente rápido para usarlo.


Me parece que a menos que el compilador pueda probar que ereport() no llama a exit() o abort() o algún otro mecanismo para la terminación del programa, esta optimización no es válida. El estándar de lenguaje menciona varios mecanismos para la terminación, e incluso define la terminación "normal" del programa mediante el retorno desde main() en términos de la función exit() .

Sin mencionar que la terminación del programa no es necesaria para evitar la expresión de división. for (;;) {} es perfectamente válido C.


No, en el estándar C más nuevo, C11, hay incluso una nueva palabra clave para especificar que una función no volverá, _Noreturn .


Se asume que la mayoría de las funciones eventualmente regresan. Hay extensiones específicas del compilador en algunos compiladores para informar al compilador que una función nunca regresará.

__attribute__ ((noreturn)) hace esto para gcc.