Funciones en línea de C y error "externo indefinido"
-
29-10-2019 - |
Pregunta
Estoy intentando reemplazar algunas subrutinas macro con funciones en línea, para que el compilador pueda optimizarlas, para que el depurador pueda intervenir en ellas, etc.Si las defino como funciones normales funciona:
void do_something(void)
{
blah;
}
void main(void)
{
do_something();
}
pero si los defino como en línea:
inline void do_something(void)
{
blah;
}
void main(void)
{
do_something();
}
dice "Error:Externo indefinido".¿Qué significa eso?Apuñalando en la oscuridad, lo intenté
static inline void do_something(void)
{
blah;
}
void main(void)
{
do_something();
}
y no más errores.La definición de la función y la llamada a la función están en el mismo archivo .c.
¿Alguien puede explicar por qué uno funciona y el otro no?
(Segunda pregunta relacionada:¿Dónde pongo funciones en línea si quiero usarlas en más de un archivo .c?)
Solución
Primero, el compilador no siempre incluye funciones marcadas como inline
;por ejemplo, si desactiva todas las optimizaciones, probablemente no las incorporará.
Cuando defines una función en línea
inline void do_something(void)
{
blah
}
y usar esa función, incluso en el mismo archivo, la llamada a esa función la resuelve el vinculador, no el compilador, porque es implícitamente "externo".Pero esta definición por sí sola no proporciona una definición externa de la función.
Si incluye una declaración sin inline
void do_something(void);
en un archivo C que puede ver el inline
definición, el compilador proporcionará una definición externa de la función y el error debería desaparecer.
La razón static inline
funciona es que hace que la función sea visible solo dentro de esa unidad de compilación y, por lo tanto, permite que el compilador resuelva la llamada a la función (y la optimice) y emita el código para la función dentro de esa unidad de compilación.Entonces el enlazador no tiene que resolverlo, por lo que no hay necesidad de una definición externa.
El mejor lugar para colocar funciones en línea es en un archivo de encabezado y declararlas static inline
.Esto elimina la necesidad de una definición externa, por lo que resuelve el problema del vinculador.Sin embargo, esto hace que el compilador emita el código para la función en cada unidad de compilación que lo utiliza, por lo que podría generar un exceso de código.Pero como la función está en línea, probablemente sea pequeña de todos modos, por lo que esto no suele ser un problema.
La otra opción es definir como extern inline
en el encabezado y en un archivo C proporcione y extern
declaración sin el inline
modificador.
El manual de gcc lo explica así:
Al declarar una función en línea, puede dirigir GCC que haga llamadas a esa función más rápido.Una forma en que GCC puede lograr esto es integrar el código de esa función en el código para sus personas que llaman.Esto hace que la ejecución sea más rápida al eliminar la sobrecarga de funciones;Además, si alguno de los valores de argumentos reales es constante, sus valores conocidos pueden permitir simplificaciones en el momento de la compilación para que no se deba incluir todo el código de la función en línea.El efecto sobre el tamaño del código es menos predecible;El código de objeto puede ser mayor o menor con la enlinición de la función, dependiendo del caso particular.También puede dirigir a GCC para intentar integrar todas las funciones "lo suficientemente simples" en sus personas que llaman con la opción
-finline-functions
.GCC implementa tres semánticas diferentes de declarar una función en línea.Uno está disponible con
-std=gnu89
o-fgnu89-inline
o cuandognu_inline
El atributo está presente en todas las declaraciones en línea, otro cuando-std=c99
,-std=c1x
,-std=gnu99
o-std=gnu1x
(sin-fgnu89-inline
), y el tercero se utiliza al compilar C++.Para declarar una función en línea, use el
inline
Palabra clave en su declaración, como esta:static inline int inc (int *a) { return (*a)++; }
Si está escribiendo un archivo de encabezado que se incluirá en los programas ISO C90, escriba
__inline__
en lugar deinline
.Los tres tipos de inserción se comportan de manera similar en dos casos importantes:cuando el
inline
palabra clave se utiliza en unstatic
función, como el ejemplo anterior, y cuando una función se declara por primera vez sin usar elinline
palabra clave y luego se define coninline
, como esto:extern int inc (int *a); inline int inc (int *a) { return (*a)++; }
En ambos casos comunes, el programa se comporta igual que si no hubiera utilizado el
inline
palabra clave, excepto por su velocidad.Cuando una función está tanto en línea como
static
, si todas las llamadas a la función están integradas en la persona que llama, y la dirección de la función nunca se usa, entonces nunca se hace referencia al propio código de ensamblador de la función.En este caso, GCC en realidad no genera código de ensamblador para la función, a menos que especifique la opción-fkeep-inline-functions
.Algunas llamadas no pueden integrarse por varias razones (en particular, las llamadas que preceden a la definición de la función no se pueden integrar, y ninguno de los dos puede recursivo dentro de la definición).Si hay una llamada no integrada, entonces la función se compila en el código del ensamblador como de costumbre.La función también debe compilarse como de costumbre si el programa se refiere a su dirección, porque eso no se puede ingresar.Tenga en cuenta que ciertos usos en una definición de función pueden hacerlo inadecuado para la sustitución en línea.Entre estos usos se encuentran:Uso de varargs, uso de ALLOCA, uso de tipos de datos de tamaño variable, uso de GOTO calculado, uso de GOTO no local y funciones anidadas.Usando
-Winline
avisará cuando una función marcadainline
No se pudo sustituir y dará la razón del fracaso.Como lo requiere ISO C ++, GCC considera que las funciones miembros definidas dentro del cuerpo de una clase se marcan en línea incluso si no se declaran explícitamente con el
inline
palabra clave.Puedes anular esto con-fno-default-inline
.GCC no está en línea de ninguna función al no optimizar a menos que especifique el
always_inline
atributo para la función, así:/* Prototype. */ inline void foo (const char) __attribute__((always_inline));
El resto de esta sección es específico para la incorporación de GNU C90.
Cuando una función en línea no es
static
, entonces el compilador debe asumir que puede haber llamadas de otros archivos de origen;Dado que un símbolo global se puede definir solo una vez en cualquier programa, la función no debe definirse en los otros archivos de origen, por lo que las llamadas allí no pueden integrarse.Por lo tanto, un no-static
La función en línea siempre se compila por sí sola de la manera habitual.Si especifica ambos
inline
yextern
En la definición de función, entonces la definición se usa solo para insertar.En ningún caso la función compilada por sí sola, ni siquiera si se refiere a su dirección explícitamente.Tal dirección se convierte en una referencia externa, como si solo hubiera declarado la función y no la hubiera definido.Esta combinación de
inline
yextern
tiene casi el efecto de una macro.La forma de usarlo es poner una definición de función en un archivo de encabezado con estas palabras clave y poner otra copia de la definición (faltainline
yextern
) en un archivo de biblioteca.La definición en el archivo de encabezado hará que la mayoría de las llamadas a la función se ingresen.Si queda algún uso de la función, se referirán a la copia única en la biblioteca.
Otros consejos
Para inline
Funciones para trabajar con C99 (solo llegaron allí al idioma) Tendría que dar la definición en un archivo de encabezado
inline void do_something(void)
{
blah
}
y en una Unidad de compilación (también conocido como .c) Usted coloca algún tipo de "instanciación"
void do_something(void);
sin el inline
.
Debe colocarlos en un archivo de encabezado si desea usarlos desde varios archivos.
Y para el error de enlazador: la declaración predeterminada de una función implica que es "externo", pero como está en línea, el enlazador puede encontrar el stub de símbolo generado por el compilador, de ahí el error.