En los lenguajes de programación C y C++ , una función en línea es aquella calificada con la palabra clave ; esto tiene dos propósitos: inline
register
especificador de clase de almacenamiento , que de manera similar proporciona una sugerencia de optimización. [1]inline
es cambiar el comportamiento de los enlaces; los detalles de esto son complicados. Esto es necesario debido al modelo de compilación y enlace separados de C/C++, específicamente porque la definición (cuerpo) de la función debe duplicarse en todas las unidades de traducción donde se usa, para permitir la inserción en línea durante la compilación , lo que, si la función tiene un enlace externo , causa una colisión durante el enlace (viola la unicidad de los símbolos externos). C y C++ (y dialectos como GNU C y Visual C++) resuelven esto de diferentes maneras. [1]Una inline
función se puede escribir en C o C++ de la siguiente manera:
intercambio de vacío en línea ( int * m , int * n ) { int tmp = * m ; * m = * n ; * n = tmp ; }
Luego, una afirmación como la siguiente:
intercambiar ( & x , & y );
se puede traducir a (si el compilador decide realizar la incrustación, lo que generalmente requiere que se habilite la optimización):
int tmp = x ; x = y ; y = tmp ;
Al implementar un algoritmo de clasificación que realiza muchos intercambios, esto puede aumentar la velocidad de ejecución.
C++ y C99 , pero no sus predecesores K&R C y C89 , tienen soporte para inline
funciones, aunque con diferente semántica. En ambos casos, inline
no fuerza la inserción en línea; el compilador es libre de elegir no insertar la función en línea en absoluto, o solo en algunos casos. Los diferentes compiladores varían en la complejidad de la función que pueden manejar para insertar en línea. Los compiladores de C++ convencionales como Microsoft Visual C++ y GCC admiten una opción que permite a los compiladores insertar en línea automáticamente cualquier función adecuada, incluso aquellas que no están marcadas como inline
funciones. Sin embargo, simplemente omitir la inline
palabra clave para dejar que el compilador tome todas las decisiones de inserción en línea no es posible, ya que el enlazador se quejará de definiciones duplicadas en diferentes unidades de traducción. Esto se debe a que inline
no solo le da al compilador una pista de que la función debe insertarse en línea, sino que también tiene un efecto sobre si el compilador generará una copia fuera de línea invocable de la función (consulte las clases de almacenamiento de funciones en línea).
GNU C , como parte del dialecto gnu89 que ofrece, tiene soporte para inline
C89 como extensión. Sin embargo, la semántica difiere tanto de la de C++ como de la de C99. armcc en modo C90 también se ofrece inline
como una extensión no estándar, con una semántica diferente a la de gnu89 y C99.
Algunas implementaciones proporcionan un medio por el cual forzar al compilador a incorporar una función, generalmente por medio de especificadores de declaración específicos de la implementación:
__forceinline
__attribute__((always_inline))
o __attribute__((__always_inline__))
, el último de los cuales es útil para evitar un conflicto con una macro definida por el usuario llamada always_inline
.El uso indiscriminado de este tipo de funciones puede generar un código más grande (archivo ejecutable inflado), una mejora mínima o nula del rendimiento y, en algunos casos, incluso una pérdida de rendimiento. Además, el compilador no puede incorporar la función en línea en todas las circunstancias, incluso cuando se fuerza la incorporación; en este caso, tanto gcc como Visual C++ generan advertencias.
Forzar la inserción en línea es útil si:
inline
no es respetado por el compilador (ignorado por el analizador de costo/beneficio del compilador)Para la portabilidad del código, se pueden utilizar las siguientes directivas de preprocesador:
#ifdef _MSC_VER #define forceinline __forceinline #elif definido(__GNUC__) #define forceinline en línea __atributo__((__siempre_en_línea__)) #elif definido(__CLANG__) #if __tiene_atributo(__siempre_en_línea__) #define forceinline en línea __atributo__((__siempre_en_línea__)) #else #define forceinline en línea #endif #else #define forceinline en línea #endif
static inline
tiene los mismos efectos en todos los dialectos C y C++. Emitirá una copia visible localmente (fuera de línea) de la función si es necesario.
Independientemente de la clase de almacenamiento, el compilador puede ignorar el inline
calificador y generar una llamada de función en todos los dialectos C y C++.
El efecto de la clase de almacenamiento extern
cuando se aplica o no a inline
funciones difiere entre los dialectos C [2] y C++. [3]
En C99, una función definida inline
nunca emitirá, y extern inline
siempre lo hará, una función visible externamente. A diferencia de C++, no hay forma de solicitar que una función visible externamente compartida entre unidades de traducción se emita solo si es necesario.
Si inline
las declaraciones se mezclan con extern inline
declaraciones o con declaraciones no calificadas (es decir, sin inline
calificador o clase de almacenamiento), la unidad de traducción debe contener una definición (sin importar si es no calificada, inline
o extern inline
) y se emitirá una función visible externamente para ella.
Una función definida inline
requiere exactamente una función con ese nombre en algún otro lugar del programa que esté definida extern inline
o no tenga calificador. Si se proporciona más de una definición de este tipo en todo el programa, el enlazador se quejará de los símbolos duplicados. Sin embargo, si falta alguna, el enlazador no se queja necesariamente, porque, si todos los usos pudieran incluirse en línea, no sería necesario. Pero puede quejarse, ya que el compilador siempre puede ignorar el inline
calificador y generar llamadas a la función en su lugar, como suele suceder si el código se compila sin optimización. (Este puede ser el comportamiento deseado, si se supone que la función debe incluirse en línea en todas partes por todos los medios, y se debería generar un error si no lo está). Una forma conveniente es definir las inline
funciones en archivos de encabezado y crear un archivo .c por función, que contenga una extern inline
declaración para ella e incluya el archivo de encabezado respectivo con la definición. No importa si la declaración está antes o después de la inclusión.
Para evitar que se agregue código inalcanzable al ejecutable final si todos los usos de una función se incluyen en línea, se recomienda [3] colocar los archivos de objeto de todos esos archivos .c con una sola extern inline
función en un archivo de biblioteca estáticaar rcs
, generalmente con , y luego vincular con esa biblioteca en lugar de los archivos de objeto individuales. Eso hace que solo se vinculen los archivos de objeto que realmente se necesitan, en contraste con vincular los archivos de objeto directamente, lo que hace que siempre se incluyan en el ejecutable. Sin embargo, el archivo de biblioteca debe especificarse después de todos los demás archivos de objeto en la línea de comandos del enlazador, ya que el enlazador no considerará las llamadas desde los archivos de objeto especificados después del archivo de biblioteca a las funciones. El enlazador resolverá automáticamente las llamadas desde inline
funciones a otras funciones (la opción en garantiza esto).inline
s
ar rcs
Una solución alternativa es utilizar la optimización del tiempo de enlace en lugar de una biblioteca. gcc proporciona la opción -Wl,--gc-sections
de omitir las secciones en las que no se utilizan todas las funciones. Este será el caso de los archivos de objetos que contengan el código de una única extern inline
función no utilizada. Sin embargo, también elimina todas las demás secciones no utilizadas de todos los demás archivos de objetos, no sólo las relacionadas con extern inline
funciones no utilizadas. (Puede que se desee vincular funciones en el ejecutable que el programador debe llamar desde el depurador en lugar de hacerlo el propio programa, por ejemplo, para examinar el estado interno del programa). Con este enfoque, también es posible utilizar un único archivo .c con todas extern inline
las funciones en lugar de un archivo .c por función. A continuación, el archivo debe compilarse con -fdata-sections -ffunction-sections
. Sin embargo, la página del manual de gcc advierte sobre ello, diciendo "Utilice estas opciones sólo cuando haya beneficios significativos al hacerlo".
Algunos recomiendan un enfoque completamente diferente, que consiste en definir funciones como static inline
en lugar de inline
en archivos de encabezado. [2] De este modo, no se generará ningún código inalcanzable. Sin embargo, este enfoque tiene un inconveniente en el caso opuesto: se generará código duplicado si la función no se pudo incluir en línea en más de una unidad de traducción. El código de función emitido no se puede compartir entre unidades de traducción porque debe tener direcciones diferentes. Este es otro inconveniente; tomar la dirección de una función de este tipo definida como static inline
en un archivo de encabezado producirá valores diferentes en diferentes unidades de traducción. Por lo tanto, static inline
las funciones solo se deben utilizar si se utilizan en una sola unidad de traducción, lo que significa que solo deben ir al archivo .c respectivo, no a un archivo de encabezado.
La semántica de gnu89 de inline
y extern inline
es esencialmente exactamente lo opuesto a la de C99, [4] con la excepción de que gnu89 permite la redefinición de una extern inline
función como una función no calificada, mientras que C99 inline
no. [5] Por lo tanto, gnu89 extern inline
sin redefinición es como C99 inline
, y gnu89 inline
es como C99 extern inline
; en otras palabras, en gnu89, una función definida inline
siempre emitirá y una función definida extern inline
nunca emitirá una función visible externamente. La razón para esto es que coincide con las variables, para las cuales nunca se reservará almacenamiento si se define como extern
y siempre si se define sin. La razón para C99, en contraste, es que sería asombroso si el uso inline
tuviera un efecto secundario (emitir siempre una versión no incorporada de la función) que es contrario a lo que sugiere su nombre.
Las observaciones para C99 sobre la necesidad de proporcionar exactamente una instancia de función visible externamente para funciones en línea y sobre el problema resultante con código inalcanzable se aplican mutatis mutandis también a gnu89.
gcc hasta la versión 4.2 inclusive usaba inline
semántica gnu89 incluso cuando -std=c99
se especificaba explícitamente. [6] Con la versión 5, [5] gcc cambió de gnu89 al dialecto gnu11, habilitando efectivamente inline
la semántica C99 por defecto. Para usar la semántica gnu89 en cambio, deben habilitarse explícitamente, ya sea con -std=gnu89
o , para afectar solo la inserción en línea , -fgnu89-inline
o agregando el gnu_inline
atributo a todas inline
las declaraciones. Para asegurar la semántica C99, se puede usar , o (sin ) -std=c99
. [ 3 ]-std=c11
-std=gnu99
-std=gnu11
-fgnu89-inline
En C++, una función definida inline
emitirá, si es necesario, una función compartida entre las unidades de traducción, normalmente colocándola en la sección común del archivo de objeto para el que se necesita. La función debe tener la misma definición en todas partes, siempre con el inline
calificador. En C++, extern inline
es lo mismo que inline
. La razón fundamental del enfoque de C++ es que es la forma más conveniente para el programador, ya que no se deben tomar precauciones especiales para la eliminación de código inalcanzable y, al igual que para las funciones ordinarias, no hay diferencia si extern
se especifica o no.
El inline
calificador se agrega automáticamente a una función definida como parte de una definición de clase.
armcc en modo C90 proporciona extern inline
una inline
semántica que es la misma que en C++: dichas definiciones emitirán una función compartida entre las unidades de traducción si es necesario. En modo C99, extern inline
siempre emite una función, pero al igual que en C++, se compartirá entre las unidades de traducción. Por lo tanto, la misma función se puede definir extern inline
en diferentes unidades de traducción. [7] Esto coincide con el comportamiento tradicional de los compiladores C de Unix [8] para múltiples extern
definiciones no inicializadas de variables globales.
Para tomar la dirección de una inline
función es necesario que se emita código para que se emita una copia no en línea de esa función en cualquier caso.
En C99, una función inline
or no debe acceder a variables globales ni definir variables no locales. Las variables locales pueden ser o no objetos diferentes en diferentes unidades de traducción, dependiendo de si la función se incluyó en línea o si se realizó una llamada. Solo las definiciones pueden hacer referencia a identificadores con enlace interno sin restricciones; estos serán objetos diferentes en cada unidad de traducción. En C++, se permiten tanto las variables locales como las no locales y hacen referencia al mismo objeto en todas las unidades de traducción.extern inline
static
const
static
const static
static inline
const
const
static
gcc no puede incorporar funciones en línea si [3]
alloca
goto
goto
setjmp
__builtin_longjmp
__builtin_return
, o__builtin_apply_args
Según las especificaciones de Microsoft en MSDN, MS Visual C++ no puede incorporarse en línea (ni siquiera con __forceinline
), si
#pragma inline_recursion(on)
. Con pragma, las funciones recursivas se incorporan en línea a una profundidad predeterminada de 16 llamadas. Para reducir la profundidad de incorporación en línea, utilice inline_depth
pragma.__declspec
modificador naked.Además de los problemas con la expansión en línea en general (ver Expansión en línea § Efecto en el rendimiento ), inline
las funciones como una característica del lenguaje pueden no ser tan valiosas como parecen, por varias razones:
inline
función) queda expuesto a su cliente (la función que la llama).inline
C99 requiere exactamente una definición externa de la función, si se utiliza en algún lugar. Si el programador no proporciona dicha definición, eso puede conducir fácilmente a errores de enlazador. Esto puede suceder con la optimización desactivada, lo que normalmente impide la inserción en línea. Por otro lado, agregar las definiciones puede provocar un código inalcanzable si el programador no lo evita cuidadosamente, colocándolas en una biblioteca para enlazar, utilizando la optimización del tiempo de enlace o static inline
.inline
función en cada módulo (unidad de traducción) que la utiliza, mientras que una función ordinaria debe definirse en un solo módulo. De lo contrario, no sería posible compilar un solo módulo independientemente de todos los demás módulos. Dependiendo del compilador, esto puede provocar que cada archivo de objeto respectivo contenga una copia del código de la función, para cada módulo con algún uso que no se pudo incluir en línea.Una declaración de función ... con un especificador en línea declara una función en línea. El especificador en línea indica a la implementación que se debe preferir la sustitución en línea del cuerpo de la función en el punto de llamada al mecanismo de llamada de función habitual. No se requiere que una implementación realice esta sustitución en línea en el punto de llamada; sin embargo, incluso si se omite esta sustitución en línea, se respetarán las demás reglas para funciones en línea definidas en 7.1.2.
— ISO/IEC 14882:2011, el estándar actual de C++, sección 7.1.2
Una función declarada con un especificador de función en línea es una función en línea ... Hacer que una función sea una función en línea sugiere que las llamadas a la función sean lo más rápidas posible. La medida en que tales sugerencias son efectivas está definida por la implementación ( nota al pie: por ejemplo, una implementación podría no realizar nunca una sustitución en línea, o podría realizar únicamente sustituciones en línea a llamadas dentro del alcance de una declaración en línea ) .
... Una definición en línea no proporciona una definición externa para la función y no prohíbe una definición externa en otra unidad de traducción . Una definición en línea proporciona una alternativa a una definición externa, que un traductor puede usar para implementar cualquier llamada a la función en la misma unidad de traducción. No se especifica si una llamada a la función usa la definición en línea o la definición externa.
— ISO 9899:1999(E), la norma C99, sección 6.7.4
{{cite journal}}
: Requiere citar revista |journal=
( ayuda )-fno-common