Funciones en línea en C ++. ¿Cuál es el punto de?

19

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?

EpsilonVector
fuente
1
Aquí . Página 6
EpsilonVector

Respuestas:

40

inlinees de C; no era nuevo en C ++.

Hay palabras clave C ( registery inline) 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 inlinefunción en un encabezado sin problemas, y las funciones de miembro definidas en una definición de clase son automáticamente efectivas inline.

David Thornley
fuente
3
+1 para la historia de inline.
Tamara Wijsman
11
Estoy bastante seguro de que inlineprimero 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.
Ben Voigt
@Ben Voigt: Tienes razón. Lo encontré por primera vez en C.
David Thornley
55
También tenga en cuenta que no solo el historial es incorrecto, sino que las reglas para inlineC99 y posteriores son diferentes de las reglas para inlineC ++.
Alf P. Steinbach
27

Originalmente inlineera una pista muy fuerte de que las llamadas a la función deberían estar en línea.

Pero el único efecto garantizado de inlinees 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 typedefparámetro de plantilla void(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 inlinevariables serán compatibles con C ++ 17 .

Alf P. Steinbach
fuente
1
+1 para la información adicional sobre inline.
Tamara Wijsman
1
" efectivamente idéntico " es en realidad una condición muy complicada, ya que los lenguajes C ++ muy mal diseñados se esfuerzan mucho para que los programadores normalmente competentes y cuidadosos escriban código razonable pero formalmente indefinido que usa objetos estáticos. Me pregunto cuántos miembros del comité caen en la trampa.
curioso
6

¿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.

Charles E. Grant
fuente
55
Pero el compilador puede (¡y lo hace!) Resolver esto mucho mejor que el programador en general. Entonces este no es un argumento válido.
Konrad Rudolph el
" cada llamada a la función ahora debe reemplazarse con el código completo de la función " Y la función en línea no es una función donde se omite la secuencia de llamadas y donde se copia el código de ensamblaje de la función. Una función en línea se compila en su lugar, incorporando el código intermedio de alto nivel en línea. Eso permite que el compilador trate el cuerpo de la función de manera efectiva como una macro limpia (una macro limpia es aquella que no tiene las peculiaridades de las macros de preprocesador C), con potencialmente muchas optimizaciones disponibles.
curioso
3

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.

Ian
fuente
2

Veamos lo que dice el estándar (partes importantes resaltadas en negrita):

2. 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 la sustitución en línea del cuerpo de la función en el punto de llamada debe preferirse al mecanismo habitual de llamada a la función. No se requiere una implementación para realizar esta sustitución en línea en el punto de llamada; sin embargo, incluso si se omite esta sustitución en línea, se seguirán respetando las otras reglas para las funciones en línea.

- Estándar C ++, ISO / IEC 14882: 2003 , 7.1.2 Especificadores de funciones [dcl.fct.spec]

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:

No hay respuestas simples: tienes que jugar con él para ver qué es lo mejor. No se conforme con respuestas simplistas como "Nunca use inlinefunciones" o "Siempre use inlinefunciones" o "Use inlinefunciones si y solo si la función tiene menos de N líneas de código". Estas reglas de talla única pueden ser fáciles de escribir, pero producirán resultados subóptimos.

- Preguntas frecuentes de C ++, Funciones en línea , 9.3 ¿Las inlinefunciones mejoran el rendimiento?

Tamara Wijsman
fuente
1
Lo que usted dice es cierto, pero no es relevante porque, como programador, no es su decisión si está en línea o no. Pones la palabra clave, puedes o no poner tus funciones en línea. No pones la palabra clave, todavía puedes o no ponerla en línea. No tiene sentido tener NINGUNA regla para cuando pones la palabra clave, ya que el compilador es el que realmente toma la decisión.
Kate Gregory el
¿Cómo no es esto relevante si dice exactamente lo mismo que tú? No hay respuestas simples .
Tamara Wijsman
No es relevante porque no responde la pregunta de los OP. No está preguntando "qué hace en línea", está preguntando "cuál es el punto". Y su respuesta solo reitera lo que todos ya sabemos acerca de la línea.
Davor Ždralo
@Davor: "¿Cuál es el punto?" no se adhiere a las preguntas frecuentes, por lo que estoy respondiendo las preguntas formuladas dentro de la pregunta. Estoy diciendo el punto inlineal 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 ...
Tamara Wijsman
¿Por qué demonios no se adheriría a las preguntas frecuentes? El estándar describe la sintaxis y la semántica de la palabra clave en línea, y la pregunta de los OP es ¿cuál es el propósito, cuándo debe usarse, qué problemas se supone que debe resolver? No veo cómo eso no se adhiere a las preguntas frecuentes.
Davor Ždralo
1

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.

Max Kielland
fuente
Eh que? Poner en línea en su código no significa que el compilador realmente lo incorporará, y no ponerlo allí tampoco significa que no lo hará. Es solo una pista, que los compiladores de hoy ignoran en su mayoría. Si al compilador le resulta útil alinear, entonces, de lo contrario, no lo hará. No tienes control real allí. OP ya lo sabe, y pregunta, cito: "¿Cuál es el punto?". ¿Quién diablos da +1 en esto? Es a la vez engañoso (lo que implica que la palabra clave en línea impone la inlínea) e irrelevante para la pregunta.
Davor Ždralo
2
@Davor Ždralo No hay necesidad de ser grosero. Interpreté la pregunta como ¿POR QUÉ debo usar inlines? Mi punto era mostrar que puedes obtener el código más rápido a costa de la memoria. Cada compilador puede manejar la palabra clave en línea de manera diferente, por lo que debe verificar la documentación, que no mencioné. Dado que no siempre puede permitirse el gasto adicional de memoria que genera la sobrecarga en línea, es útil "guiar" cuándo usarlo. Tampoco dije que se apliquen las líneas. Alguien encontró esta respuesta útil para él / ella y votó +1, respete que otro puede tener otro nivel de experiencia y encuentre algo trivial útil.
Max Kielland