proceso funciona compilar compilador compilacion como c gcc gcc-warning noreturn

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 (como const ) 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 tu your_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.

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.