Según lo que leí, el compilador no está obligado a sustituir la llamada a la función de una función en línea con su cuerpo, pero lo hará si puede. Esto me hizo pensar: ¿por qué tenemos la palabra en línea si ese es el caso? ¿Por qué no hacer todas las funciones en línea de forma predeterminada y dejar que el compilador descubra si puede sustituir las llamadas con el cuerpo de la función o no?
c++
language-design
optimization
compilation
EpsilonVector
fuente
fuente
Respuestas:
inline
es de C; no era nuevo en C ++.Hay palabras clave C (
register
yinline
) que fueron diseñadas para permitir que el programador ayude en la optimización del código. En general, actualmente se ignoran, ya que los compiladores pueden mejorar en la asignación de registros y decidir cuándo incorporar funciones (de hecho, un compilador puede incorporar o no una función en diferentes momentos). La generación de código en procesadores modernos es mucho más complicada que en los más deterministas comunes cuando Ritchie estaba inventando C.Lo que la palabra significa ahora, en C ++, es que puede tener múltiples definiciones idénticas y debe definirse en cada unidad de traducción que la utiliza. (En otras palabras, debe asegurarse de que pueda estar en línea). Puede tener una
inline
función en un encabezado sin problemas, y las funciones de miembro definidas en una definición de clase son automáticamente efectivasinline
.fuente
inline
.inline
primero se estandarizó en C ++, aunque ya estaba disponible como una extensión de proveedor en C ... sí, parece que se agregó al estándar C en C99.inline
C99 y posteriores son diferentes de las reglas parainline
C ++.Originalmente
inline
era una pista muy fuerte de que las llamadas a la función deberían estar en línea.Pero el único efecto garantizado de
inline
es permitir que se defina una función (de manera idéntica) en múltiples unidades de traducción, por ejemplo, que coloque la definición en un archivo de encabezado.Hoy en día, algunos compiladores están muy interesados en seguir la pista de alineación, por ejemplo, g ++. Y algunos compiladores lo toman menos en serio, por ejemplo, Visual C ++. Pero todos tienen que cumplir con la garantía.
Es desafortunado que estos dos significados (sugerencia de optimización y lo que podríamos llamar una definición descartable de nivel de enlazador) residan con la misma palabra clave, porque significa que prácticamente no se puede tener uno sin el otro.
También es lamentable que
inline
(o mejor, una palabra clave separada sobre definición descartable) no se pueda aplicar a los datos .La necesidad de datos descartables a nivel de enlazador ha aumentado a medida que los módulos de solo encabezado se han vuelto más populares. Por ejemplo, muchas sub-bibliotecas de Boost son solo de encabezado.
Sin embargo, para los datos puede aplicar un pequeño truco con plantillas. Definirlo en alguna plantilla de clase, proporcionar un
typedef
parámetro de plantillavoid
(o lo que sea). Esto se debe a que la regla de una definición hace una excepción específica para las plantillas.Notas:
¹ las
inline
variables serán compatibles con C ++ 17 .fuente
inline
.¿Por qué no hacer que todas las funciones estén en línea por defecto? Porque es un intercambio de ingeniería. Existen al menos dos tipos de "optimización": acelerar el programa y reducir el tamaño (huella de memoria) del programa. La alineación generalmente acelera las cosas. Elimina la sobrecarga de la llamada de función, evitando empujar y luego sacar parámetros de la pila. Sin embargo, también aumenta la huella de memoria del programa, porque cada llamada a la función ahora debe reemplazarse con el código completo de la función. Para complicar aún más las cosas, recuerde que la CPU almacena fragmentos de memoria usados con frecuencia en un caché en la CPU para un acceso ultrarrápido. Si hace que la imagen de memoria del programa sea lo suficientemente grande, su programa no podrá usar la memoria caché de manera eficiente y, en el peor de los casos, la alineación podría ralentizar su programa.
fuente
Para entender "en línea" necesita comprender la historia y cómo era la vida hace 20 (y 30) años.
Estábamos escribiendo código en computadoras que tenían poca memoria, por lo que no era posible que un compilador procesara todo el código que formaba un programa de una sola vez. El compilador también fue muy lento, por lo que no quería tener que volver a compilar el código que no había cambiado: tomar más de 24 horas (en una computadora que costaba más que un automóvil de gama alta) para recompilar todo el código era normal para algunos proyectos. trabajado en.
Por lo tanto, cada archivo de código se compiló por separado en archivos de objetos. Cada archivo de objeto comenzó con una lista de todas las funciones que contenía, junto con la "dirección" de la función. Un archivo de objeto también tenía una lista de todas las funciones que llamó en otros archivos de objeto junto con la ubicación de la llamada.
Un enlazador primero leería todos los archivos de objetos y crearía una lista de todas las funciones que exportaron, junto con el archivo en el que estaban y su dirección. Luego volvería a leer todos los archivos de objetos, enviándolos al archivo de programa, mientras actualiza todas las llamadas de funciones "externas" con la dirección de la función.
El enlazador no modificó ni optimizó el código de máquina producido por el compilador de ninguna otra forma que no fuera para arreglar referencias a llamadas a funciones externas. El enlazador era parte del sistema operativo y es anterior a la mayoría de los compiladores. Cuando las personas escribían un nuevo compilador, lo necesitaban para trabajar con los enlazadores actuales y para poder enlazar con los archivos de objetos actuales, de lo contrario no se podrían hacer llamadas al sistema.
El compilador solo vio el código en el archivo ".c" o ".cpp" que estaba compilando junto con todos los archivos de encabezado incluidos. Por lo tanto, no podría realizar ninguna optimización basada en el código de otros archivos ".c" o ".cpp".
La palabra clave "en línea" permitió que el cuerpo de una función (método) se definiera en un archivo de encabezado, lo que permitió al compilador utilizar el código de la función mientras compilaba el código que la llama. Por ejemplo, digamos que tenía una clase de colección definida en otro archivo .cpp, esta clase tendría un método "isEmpty", que contenía una línea de código, habría una gran aceleración del programa resultante si en lugar de una llamada a una función , la llamada a la función se reemplazó con esta línea.
La palabra clave "en línea" se consideraba en ese momento como una forma "barata y fácil" de permitir la encapsulación de datos y evitar el costo de las llamadas a funciones, sin que muchos programadores hubieran accedido a los campos privados del objeto. (Las macros eran una forma mucho peor de "alinear" el código que cuando era común en ese momento).
En la actualidad, los "enlazadores" optimizan mucho el código y tienden a ser escritos por algún equipo como compilador. El compilador a menudo solo comprueba que el código es correcto y lo "comprime", dejando la mayor parte de la tarea de creación de código de máquina al enlazador.
fuente
Veamos lo que dice el estándar (partes importantes resaltadas en negrita):
Entonces, si quieres estar seguro, debes leer la documentación de tu compilador.
Incluir todo en línea es una mala idea, ya que podría generar una gran cantidad de código de máquina duplicado ...
Entonces tendrás que saber:
fuente
inline
al reiterar el conocimiento ya que el OP parece perder una parte, por lo que es la razón principal por la que no entiende el punto. Para que alguien obtenga un punto, necesita comprender qué hay debajo de ese punto ...Permítame darle una buena razón para usar la palabra clave en línea.
En un sistema integrado, como una impresora de tickets o un sistema similar más pequeño. El procesador es muy limitado y una llamada de función (preparar parámetros de función en la pila, llamar, recuperar parámetros de la pila y devolver la respuesta, etc.) puede tardar varios ms en ejecutarse, además de la función misma.
Digamos que el tiempo de llamada es de aproximadamente 60 ms (solo para llamar, no la función real) y tiene que hacer 50 iteraciones (llamadas en bucle o iterativas en un árbol).
El tiempo para avanzar y retroceder desde esa llamada de función tomará 60 * 50 = 3000 (3 segundos).
Si tienes memoria, definitivamente harías una línea para ahorrar 3 segundos.
Así que en línea se usan básicamente cuando necesitas velocidad de ejecución. En algunos proyectos en los que estuve involucrado, el tiempo de llamada era más largo que el tiempo de ejecución, una situación clásica cuando se usa en línea.
fuente