¿Es útil "en línea" sin "estático" o "externo" en C99?

Resuelto Sven Marnach asked hace 13 años • 3 respuestas

Cuando intento 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 vinculador (referencia indefinida a f). El error desaparece si uso static inlineo extern inlineen lugar de just inline, o si compilo con -O(por lo que la función en realidad está integrada).

Este comportamiento parece estar definido en el párrafo 6.7.4 (6) de la norma C99:

Si todas las declaraciones de alcance de archivo para una función en una unidad de traducción incluyen el inlineespecificador 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 utiliza 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 inlinecomo en el ejemplo anterior solo se compila consistentemente 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 inlinesin statico externen 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 la definición de la función

inline void f() {}

en el archivo de encabezado y solo una declaración

extern inline void f();

en el archivo .c correspondiente. Sólo la externdeclaración desencadena la generación de un código binario visible externamente. Y, de hecho, no se utiliza inlineen un archivo .c; solo es útil en los encabezados.

Como explica el fundamento del comité C99 citado en la respuesta de Jonathan , inlinese trata de optimizaciones del compilador que requieren que la definición de una función sea visible en el sitio de una llamada. Esto sólo se puede lograr poniendo 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 incorporar una función, debe existir una definición externa en alguna parte.

Sven Marnach avatar Jun 11 '11 05:06 Sven Marnach
Aceptado

En realidad, esta excelente respuesta también responde a tu 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 objeto debe contener el código generado (visible externamente).

[actualizar, para elaborar]

No creo que tenga ningún uso "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 "externa en línea" correspondiente en algún archivo .c para generar realmente el código independiente.

Nemo avatar Jun 10 '2011 22:06 Nemo

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

Apéndice J.2 Comportamiento indefinido

  • ...
  • Una función con enlace externo se declara con un inlineespecificador de función, pero tampoco se define en la misma unidad de traducción (6.7.4).
  • ...

El Comité C99 redactó una justificación que dice:

6.7.4 Especificadores de funciones

Una nueva característica de C99: la inlinepalabra clave, adaptada de C++, es un especificador de función que sólo se puede utilizar 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 un enlace interno o si tiene un enlace externo y la llamada está en la misma unidad de traducción que la definición externa. En estos casos, la presencia de la inlinepalabra 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 lugar de las llamadas de otras funciones declaradas sin la inlinepalabra 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, la inlinepalabra 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 esta ú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 se produce una llamada a alguna función funccon enlace externo donde una definición en línea es visible, el comportamiento es el mismo que si la llamada se realizara a otra función, por ejemplo __func, con enlace interno. Un programa conforme no debe depender de qué función se llama. Este es el modelo en línea del Estándar.

Un programa conforme no debe depender de la implementación que utiliza la definición en línea, ni puede depender de 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 saddry usar la definición externa para la otra, no se garantiza que la operación de igualdad se evalúe como 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 un no constobjeto de este tipo.

La inserción se agregó al estándar de tal manera que se puede implementar con la tecnología de enlace 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 contenga la definición de una función en línea como la que proporciona la definición externa de la función. Debido a que esa especificación consiste simplemente en una declaración a la que le falta la inlinepalabra clave o contiene ambas inliney extern, también será aceptada por un traductor de C++.

Inlining in C99 does extend the C++ specification in two ways. First, if a function is declared inline in one translation unit, it need not be declared inline in every other translation unit. This allows, for example, a library function that is to be inlined within the library but available only through an external definition elsewhere. The alternative of using a wrapper function for the external function requires an additional name; and it may also adversely impact performance if a translator does not actually do inline substitution.

Second, the requirement that all definitions of an inline function be “exactly the same” is replaced by the requirement that the behavior of the program should not depend on whether a call is implemented with a visible inline definition, or the external definition, of a function. This allows an inline definition to be specialized for its use within a particular translation unit. For example, the external definition of a library function might include some argument validation that is not needed for calls made from other functions in the same library. These extensions do offer some advantages; and programmers who are concerned about compatibility can simply abide by the stricter C++ rules.

Note that it is not appropriate for implementations to provide inline definitions of standard library functions in the standard headers because this can break some legacy code that redeclares standard library functions after including their headers. The inline keyword is intended only to provide users with a portable way to suggest inlining of functions. Because the standard headers need not be portable, implementations have other options along the lines of:

#define abs(x) __builtin_abs(x)

or other non-portable mechanisms for inlining standard library functions.

Jonathan Leffler avatar Jun 10 '2011 22:06 Jonathan Leffler