¿Es “en línea” sin “estático” o “externo” alguna vez útil en C99?

12 minutos de lectura

avatar de usuario
Sven Marnach

Cuando trato de construir este código

inline void f() {}

int main()
{
    f();
}

usando la línea de comando

gcc -std=c99 -o a a.c

Recibo un error del enlazador (referencia indefinida a f). El error desaparece si uso static inline o extern inline en lugar de solo inlineo si compilo con -O (por lo que la función está realmente en línea).

Este comportamiento parece estar definido en el párrafo 6.7.4 (6) del estándar C99:

Si todas las declaraciones de ámbito de archivo para una función en una unidad de traducción incluyen el inline especificador de función sin extern, entonces la definición en esa unidad de traducción es una definició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.

Si entiendo todo esto correctamente, una unidad de compilación con una función definida inline como en el ejemplo anterior, solo se compila de manera consistente si también hay una función externa con el mismo nombre, y nunca sé si se llama a mi propia función o a la función externa.

¿No es este comportamiento completamente tonto? ¿Alguna vez es útil definir una función? inline sin static o extern en C99? ¿Me estoy perdiendo de algo?

Resumen de respuestas

Por supuesto que me faltaba algo, y el comportamiento no es tonto. 🙂

Como explica Nemo, la idea es poner el definición de la función

inline void f() {}

en el archivo de cabecera y sólo un declaración

extern inline void f();

en el archivo .c correspondiente. Solo el extern declaración desencadena la generación de código binario visible externamente. Y de hecho no sirve de nada inline en un archivo .c; solo es útil en los encabezados.

Como explica el fundamento del comité C99 citado en la respuesta de Jonathan, inline tiene que ver con las optimizaciones del compilador que requieren que la definición de una función sea visible en el sitio de una llamada. Esto solo se puede lograr colocando la definición en el encabezado y, por supuesto, una definición en un encabezado no debe emitir código cada vez que el compilador la ve. Pero dado que el compilador no está obligado a alinear una función, debe existir una definición externa en alguna parte.

  • Duplicado limítrofe de stackoverflow.com/questions/5369888/…

    – Condez

    10 de junio de 2011 a las 22:40

  • Y también stackoverflow.com/questions/216510/extern-inline

    – Nemo

    10 de junio de 2011 a las 22:46

  • @Earlz: Gracias por el enlace. Mi pregunta es sobre la lógica detrás del párrafo citado del estándar, y si alguna vez hay casos de uso para inline sin static y extern, aunque. Desafortunadamente, ninguno de estos problemas está cubierto en esa pregunta.

    – Sven Marnach

    10 de junio de 2011 a las 22:46

  • @Nemo: Leí esa pregunta y las respuestas antes de publicar la mía. Nuevamente, no estoy preguntando “¿cómo se comporta?” sino más bien “¿cuál es la idea detrás de este comportamiento”? Estoy bastante seguro de que me estoy perdiendo algo aquí.

    – Sven Marnach

    10 de junio de 2011 a las 22:49

  • sí, por eso dije “límite”. Personalmente, creo que esto es hacer una pregunta completamente diferente.

    – Condez

    10 de junio de 2011 a las 22:57

avatar de usuario
Nemo

En realidad, esta excelente respuesta también responde a su pregunta, creo:

¿Qué hace externo en línea?

La idea es que “en línea” se pueda usar en un archivo de encabezado y luego “en línea externo” en un archivo .c. “externo en línea” es simplemente cómo le indica al compilador qué archivo de objeto debe contener el código generado (visible externamente).

[update, to elaborate]

No creo que haya ningún uso para “en línea” (sin “estático” o “externo”) en un archivo .c. Pero en un archivo de encabezado tiene sentido, y requiere una declaración “en línea externa” correspondiente en algún archivo .c para generar realmente el código independiente.

  • Sí, nunca entendí esto hasta ahora, así que gracias por preguntar :-). Es raro que el “en línea” no externo definición va en el encabezado (pero no necesariamente da como resultado la generación de ningún código), mientras que el “externo en línea” declaración va en el archivo .c y en realidad hace que se genere el código.

    – Nemo

    10 de junio de 2011 a las 22:57


  • ¿Significa esto que escribo inline void f() {} en el encabezado y extern inline void f(); en el archivo .c? Entonces, ¿la definición de la función real va en el encabezado y el archivo .c contiene una mera declaración en este caso, en orden inverso al habitual?

    – Sven Marnach

    10 de junio de 2011 a las 22:57

  • @endolith: no entiendo tu pregunta. estamos discutiendo inline sin static o extern. Por supuesto static inline está bien, pero de eso no se trata esta pregunta y respuesta.

    – Nemo

    22 de febrero de 2012 a las 21:16


  • @KunWu: El inline f() La definición en realidad no emite ningún código, por lo que da como resultado un símbolo indefinido en el momento del enlace. Él static La palabra clave hace que la definición emita código (visible localmente). Estas son las semánticas… Un compilador optimizador podría expandir el código en línea en cualquier caso, o incluso sin el inline palabra clave. En C, el inline La palabra clave tiene más que ver con las reglas de alcance y vinculación que con la inserción real.

    – Nemo

    08/02/2014 a las 17:40

  • @MatthieuMoy Necesitas usar -std=c99 en vez de -std=gnu89.

    – a3f

    17 de febrero de 2017 a las 6:19


avatar de usuario
jonathan leffler

Del propio estándar (ISO/IEC 9899:1999):

Apéndice J.2 Comportamiento indefinido

  • Una función con enlace externo se declara con un inline especificador de función, pero no está también definido en la misma unidad de traducción (6.7.4).

El Comité C99 escribió un Razón fundamentaly dice:

6.7.4 Especificadores de función

Una nueva característica de C99: Él inline La palabra clave, adaptada de C++, es una especificador de función que solo se puede usar en declaraciones de funciones. Es útil para optimizaciones de programas que requieren que la definición de una función sea visible en el sitio de una llamada. (Tenga en cuenta que el estándar no intenta especificar la naturaleza de estas optimizaciones).

La visibilidad está asegurada si la función tiene enlace interno o si tiene enlace externo y la llamada está en la misma unidad de traducción que la definición externa. En estos casos, la presencia del
inline La palabra clave en una declaración o definición de la función no tiene ningún efecto más allá de indicar una preferencia de que las llamadas de esa función deben optimizarse en preferencia a las llamadas de otras funciones declaradas sin la inline palabra clave.

La visibilidad es un problema para una llamada de una función con enlace externo donde la llamada está en una unidad de traducción diferente de la definición de la función. En este caso, el inline La palabra clave permite que la unidad de traducción que contiene la llamada también contenga una definición local o en línea de la función.

Un programa puede contener una unidad de traducción con una definición externa, una unidad de traducción con una definición en línea y una unidad de traducción con una declaración pero sin definición para una función. Las llamadas en la última unidad de traducción utilizarán la definición externa como de costumbre.

Una definición en línea de una función se considera una definición diferente a la definición externa. Si una llamada a alguna función func con enlace externo ocurre donde una definición en línea es visible, el comportamiento es el mismo que si la llamada se hiciera a otra función, digamos
__func, con enlace interno. Un programa conforme no debe depender de qué función se llama. Este es el modelo en línea en el estándar.

Un programa conforme no debe basarse en la implementación que utiliza la definición en línea, ni puede basarse en la implementación que utiliza la definición externa. La dirección de una función es siempre la dirección correspondiente a la definición externa, pero cuando esta dirección se usa para llamar a la función, se puede usar la definición en línea. Por lo tanto, es posible que el siguiente ejemplo no se comporte como se esperaba.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Dado que la implementación podría usar la definición en línea para una de las llamadas a saddr y usa la definición externa para el otro, no se garantiza que la operación de igualdad evalúe a 1 (verdadero). Esto muestra que los objetos estáticos definidos dentro de la definición en línea son distintos de su objeto correspondiente en la definición externa. Esto motivó la restricción de incluso definir una no-const objeto de este tipo.

La inserción se agregó al estándar de tal manera que se puede implementar con la tecnología de vinculación existente, y un subconjunto de la inserción C99 es compatible con C++. Esto se logró al requerir que se especificara exactamente una unidad de traducción que contiene la definición de una función en línea como la que proporciona la definición externa de la función. Porque esa especificación consiste simplemente en una declaración que o bien carece de la inline palabra clave, o contiene ambas inline y externtambién será aceptado por un traductor de C++.

La inserción en C99 amplía la especificación de C++ de dos maneras. Primero, si se declara una función
inline en una unidad de traducción, no es necesario declarar inline en cualquier otra unidad de traducción. Esto permite, por ejemplo, una función de biblioteca que debe estar en línea dentro de la biblioteca pero disponible solo a través de una definición externa en otro lugar. La alternativa de usar una función contenedora para la función externa requiere un nombre adicional; y también puede afectar negativamente el rendimiento si un traductor no realiza realmente la sustitución en línea.

En segundo lugar, el requisito de que todas las definiciones de una función en línea sean “exactamente iguales” se reemplaza por el requisito de que el comportamiento del programa no debe depender de si una llamada se implementa con una definición en línea visible o la definición externa de una función. función. Esto permite que una definición en línea se especialice para su uso dentro de una unidad de traducción particular. Por ejemplo, la definición externa de una función de biblioteca podría incluir alguna validación de argumento que no sea necesaria para las llamadas realizadas desde otras funciones en la misma biblioteca. Estas extensiones ofrecen algunas ventajas; y los programadores que están preocupados por la compatibilidad pueden simplemente cumplir con las reglas más estrictas de C++.

Tenga en cuenta que es no apropiado para implementaciones para proporcionar definiciones en línea de funciones de biblioteca estándar en los encabezados estándar porque esto puede romper algún código heredado que vuelve a declarar funciones de biblioteca estándar después de incluir sus encabezados. Él inline La palabra clave está destinada únicamente a proporcionar a los usuarios una forma portátil de sugerir funciones integradas. Debido a que los encabezados estándar no necesitan ser portátiles, las implementaciones tienen otras opciones como:

#define abs(x) __builtin_abs(x)

u otros mecanismos no portátiles para incorporar funciones de biblioteca estándar.

avatar de usuario
fred foo

> Me sale un error del enlazador (referencia indefinida a f)

Funciona aquí: Linux x86-64, GCC 4.1.2. Puede ser un error en su compilador; No veo nada en el párrafo citado del estándar que prohíba el programa dado. Nótese el uso de Si en vez de si y si.

Una definición en línea proporciona una alternativa a una definición externaque un traductor puede use para implementar cualquier llamada a la función en la misma unidad de traducción.

Entonces, si conoces el comportamiento de la función f y desea llamarlo en un ciclo cerrado, puede copiar y pegar su definición en un módulo para evitar llamadas a funciones; o, puede proporcionar una definición que, para los fines del módulo actual, sea equivalente (pero omita la validación de entrada o cualquier optimización que pueda imaginar). El escritor del compilador, sin embargo, tiene la opción de optimizar el tamaño del programa.

  • El estándar establece que el compilador siempre puede asumir que hay una función externa con el mismo nombre y llamarla en lugar de la versión en línea, por lo que no creo que sea un error del compilador. Probé con gcc 4.3, 4.4 y 4.5, todos dan un error de enlace.

    – Sven Marnach

    10 de junio de 2011 a las 23:23

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad