¿Es útil "en línea" sin "estático" o "externo" en C99?
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 inline
o extern inline
en 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
inline
especificador de función sinextern
, 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 inline
como 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 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 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 extern
declaración desencadena la generación de un código binario visible externamente. Y, de hecho, no se utiliza 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
se 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.
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.
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 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
inline
palabra 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
inline
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 lugar de las llamadas de otras funciones declaradas sin lainline
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, la
inline
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 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
func
con 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
saddr
y 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 noconst
objeto 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
inline
palabra clave o contiene ambasinline
yextern
, 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 declaredinline
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.