funciona - Dile a gcc que una llamada de función no regresará
proceso de compilacion en c (2)
Estoy usando C99
bajo GCC
.
Tengo una función declarada static
en inline
en un encabezado que no puedo modificar.
La función nunca devuelve pero no está marcada como __attribute__((noreturn))
.
¿Cómo puedo llamar a la función de una manera que le diga al compilador que no regresará?
Lo estoy llamando desde mi propia función noreturn, y en parte quiero suprimir la advertencia "noreturn function devuelve" pero también quiero ayudar al optimizador, etc.
He intentado incluir una declaración con el atributo pero recibo una advertencia sobre la declaración repetida.
He intentado crear un puntero de función y aplicar el atributo a eso, pero dice que el atributo de función no puede aplicarse a una función apuntada.
Desde la función que definió y que llama a la función externa, agregue una llamada a __builtin_unreachable
que esté integrada en al menos GCC compiladores de GCC y Clang y se marque como noreturn
. De hecho, esta función no hace nada más y no debe ser llamada. Es solo aquí para que el compilador pueda inferir que la ejecución del programa se detendrá en este punto.
static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }
void your_function() __attribute__((noreturn)) {
external_function(); // the compiler thinks execution may continue ...
__builtin_unreachable(); // ... and now it knows it won''t go beyond here
}
Edición: Solo para aclarar algunos puntos planteados en los comentarios, y en general, dar un poco de contexto:
- Una función tiene solo dos formas de no regresar: bucle para siempre, o cortocircuite el flujo de control habitual (por ejemplo, arroje una excepción, salte de la función, finalice el proceso, etc.)
- En algunos casos, el compilador puede inferir y probar a través del análisis estático que una función no volverá. Incluso teóricamente, esto no siempre es posible , y como queremos que los compiladores sean rápidos, solo se detectan casos obvios / fáciles.
-
__attribute__((noreturn))
es una anotación (comoconst
) que es una forma para que el programador le informe al compilador que está absolutamente seguro de que una función no regresará. Siguiendo el principio de confianza pero verificación , el compilador intenta probar que la función no regresa. Si luego puede emitir un error si prueba que la función puede regresar, o una advertencia si no pudo probar si la función regresa o no. -
__builtin_unreachable
tiene un comportamiento indefinido porque no está destinado a ser llamado. Sólo tiene la intención de ayudar al análisis estático del compilador. De hecho, el compilador sabe que esta función no regresa, por lo que cualquier código siguiente es demostrablemente inalcanzable (excepto a través de un salto).
Una vez que el compilador haya establecido (ya sea por sí mismo o con la ayuda del programador) que algún código es inalcanzable, puede usar esta información para realizar optimizaciones como estas:
- Elimine el código repetitivo que se usa para regresar de una función a su interlocutor, si la función nunca regresa
- Propague la información de inaccesibilidad , es decir, si la única ruta de ejecución a puntos de un código es a través de un código inalcanzable, entonces este punto también es inalcanzable. Ejemplos:
- si una función no regresa, cualquier código que siga a su llamada y no sea accesible a través de saltos también será inalcanzable. Ejemplo: el código que sigue a
__builtin_unreachable()
es inalcanzable. - en particular, la única ruta para el retorno de una función es a través de un código inalcanzable, la función se puede marcar como
noreturn
. Eso es lo que pasa para tuyour_function
. - no se necesita ninguna ubicación / variable de la memoria que solo se utilice en el código inalcanzable, por lo que no es necesario configurar / calcular el contenido de dichos datos.
- cualquier cálculo que probablemente sea (1) innecesario (viñeta anterior) y (2) no tiene efectos secundarios (como funciones
pure
) puede ser eliminado.
- si una función no regresa, cualquier código que siga a su llamada y no sea accesible a través de saltos también será inalcanzable. Ejemplo: el código que sigue a
Ilustración: - La llamada a external_function
no se puede eliminar porque puede tener efectos secundarios. De hecho, ¡probablemente tenga al menos el efecto secundario de terminar el proceso! - La placa de retorno de la caldera de su your_function
puede ser retirada.
Aquí hay otro ejemplo que muestra cómo se puede eliminar el código antes del punto inalcanzable.
int compute(int) __attribute((pure)) { return /* expensive compute */ }
if(condition) {
int x = compute(input); // (1) no side effect => keep if x is used
// (8) x is not used => remove
printf("hello "); // (2) reachable + side effect => keep
your_function(); // (3) reachable + side effect => keep
// (4) unreachable beyond this point
printf("word!/n"); // (5) unreachable => remove
printf("%d/n", x); // (6) unreachable => remove
// (7) mark ''x'' as unused
} else {
// follows unreachable code, but can jump here
// from reachable code, so this is reachable
do_stuff(); // keep
}
Varias soluciones:
redeclar su función con el __attribute__
Debe intentar modificar esa función en su encabezado agregando __attribute__((noreturn))
a ella.
Puede volver a declarar algunas funciones con un nuevo atributo, como lo demuestra esta prueba estúpida (agregando un atributo a fopen
):
#include <stdio.h>
extern FILE *fopen (const char *__restrict __filename,
const char *__restrict __modes)
__attribute__ ((warning ("fopen is used")));
void
show_map_without_care (void)
{
FILE *f = fopen ("/proc/self/maps", "r");
do
{
char lin[64];
fgets (lin, sizeof (lin), f);
fputs (lin, stdout);
}
while (!feof (f));
fclose (f);
}
anulando con una macro
Por fin, podrías definir una macro como
#define func(A) {func(A); __builtin_unreachable();}
(Esto usa el hecho de que dentro de una macro, el nombre de la macro no es macro-expandido).
Si su función de no retorno está declarando como retorna, por ejemplo, int
, usará una expresión de declaración como
#define func(A) ({func(A); __builtin_unreachable(); (int)0; })
Las soluciones basadas en macros como las anteriores no siempre funcionarán, por ejemplo, si la func
se pasa como un puntero a la función, o simplemente si algún tipo codifica (func)(1)
que es legal pero feo.
redeclarización de una línea estática con el atributo noreturn
Y el siguiente ejemplo:
// file ex.c
// declare exit without any standard header
void exit (int);
// define myexit as a static inline
static inline void
myexit (int c)
{
exit (c);
}
// redeclare it as notreturn
static inline void myexit (int c) __attribute__ ((noreturn));
int
foo (int *p)
{
if (!p)
myexit (1);
if (p)
return *p + 2;
return 0;
}
cuando se compila con GCC 4.9 (de Debian / Sid / x86-64) como gcc -S -fverbose-asm -O2 ex.c
) proporciona un archivo de ensamblaje que contiene la optimización esperada:
.type foo, @function
foo:
.LFB1:
.cfi_startproc
testq %rdi, %rdi # p
je .L5 #,
movl (%rdi), %eax # *p_2(D), *p_2(D)
addl $2, %eax #, D.1768
ret
.L5:
pushq %rax #
.cfi_def_cfa_offset 16
movb $1, %dil #,
call exit #
.cfi_endproc
.LFE1:
.size foo, .-foo
Puede jugar con el diagnóstico de #pragma GCC para desactivar selectivamente una advertencia.
Personalizando GCC con MELT
Finalmente, puede personalizar su gcc
reciente con el complemento MELT y codificar su extensión simple (en el lenguaje específico del dominio MELT ) para agregar el atributo noreturn
cuando encierre la función deseada. Probablemente sea una docena de líneas MELT, usando register_finish_decl_first
y una coincidencia en el nombre de la función.
Ya que soy el autor principal de MELT (software libre GPLv3 +), quizás pueda incluso codificarlo si lo solicita, por ejemplo, aquí o preferiblemente en [email protected]
; Dale el nombre concreto de tu función de nunca regresar.
Probablemente el código MELT se ve como:
;;file your_melt_mode.melt
(module_is_gpl_compatible "GPLv3+")
(defun my_finish_decl (decl)
(let ( (tdecl (unbox :tree decl))
)
(match tdecl
(?(tree_function_decl_named
?(tree_identifier ?(cstring_same "your_function_name")))
;;; code to add the noreturn attribute
;;; ....
))))
(register_finish_decl_first my_finish_decl)
El verdadero código MELT es un poco más complejo. Desea definir your_adding_attr_mode
allí. Pídeme más.
Una vez que haya codificado su extensión MELT, your_melt_mode.melt
para sus necesidades (y compiló esa extensión MELT en your_melt_mode.quicklybuilt.so
como se documented en los tutoriales de MELT) compilará su código con
gcc -fplugin=melt /
-fplugin-arg-melt-extra=your_melt_mode.quicklybuilt /
-fplugin-arg-melt-mode=your_adding_attr_mode /
-O2 -I/your/include -c yourfile.c
En otras palabras, ¡solo agrega algunas -fplugin-*
a tus CFLAGS
en tu Makefile
!
Por cierto, solo estoy codificando en el monitor MELT (en github: https://github.com/bstarynk/melt-monitor ..., archivo meltmom-process.melt
algo bastante similar.
Con una extensión MELT, no recibirá ninguna advertencia adicional, ya que la extensión MELT alteraría el GCC AST interno (un árbol GCC) de la función declarada sobre la marcha.
La personalización de GCC con MELT es probablemente la solución más segura, ya que está modificando el AST interno de GCC. Por supuesto, es probable que sea la solución más costosa (y es específica de GCC y podría necesitar cambios pequeños cuando GCC esté evolucionando, por ejemplo, al usar la próxima versión de GCC), pero como estoy tratando de demostrar que es bastante fácil Tu caso.