Estará “inline” sem “estático” ou “extern” útil em C99?

Quando tento construir esse código

inline void f() {} int main() { f(); } 

usando a linha de comando

 gcc -std=c99 -oa ac 

Eu recebo um erro de vinculador (referência indefinida para f ). O erro desaparece se eu usar static inline ou extern inline vez de apenas inline , ou se eu compilar com -O (então a function está realmente inline).

Este comportamento parece estar definido no ponto 6.7.4 (6) da norma C99:

Se todas as declarações de escopo de arquivo para uma function em uma unidade de conversão incluírem o especificador de function inlineinline sem extern , a definição nessa unidade de tradução será uma definição in-line. Uma definição inline não fornece uma definição externa para a function e não proíbe uma definição externa em outra unidade de tradução. Uma definição inline fornece uma alternativa para uma definição externa, que um tradutor pode usar para implementar qualquer chamada para a function na mesma unidade de tradução. Não é especificado se uma chamada para a function usa a definição inline ou a definição externa.

Se eu entendi tudo isso corretamente, uma unidade de compilation com uma function definida em inline como no exemplo acima somente compila consistentemente se também houver uma function externa com o mesmo nome, e nunca sei se minha própria function ou a function externa é chamada.

Esse comportamento não é completamente idiota? É sempre útil definir uma function inline sem static ou extern em C99? Estou esquecendo de algo?

Resumo das respostas

É claro que eu estava perdendo alguma coisa e o comportamento não é idiota. 🙂

Como Nemo explica , a ideia é colocar a definição da function

 inline void f() {} 

no arquivo de header e apenas uma declaração

 extern inline void f(); 

no arquivo .c correspondente. Apenas a declaração extern aciona a geração de código binário visível externamente. E de fato não há uso de inline em um arquivo .c – é útil apenas em headers.

Como a lógica do comitê C99 citada na resposta de Jonathan explica, inline é tudo sobre otimizações de compilador que exigem a definição de uma function para ser visível no site de uma chamada. Isso só pode ser alcançado colocando a definição no header e, é claro, uma definição em um header não deve emitir código toda vez que for vista pelo compilador. Mas como o compilador não é forçado a realmente alinhar uma function, uma definição externa deve existir em algum lugar.

    Na verdade, essa excelente resposta também responde à sua pergunta, acho que:

    extern inline

    A idéia é que “inline” pode ser usado em um arquivo de header e, em seguida, “extern inline” em um arquivo .c. “extern inline” é exatamente como você instrui o compilador qual arquivo de object deve conter o código gerado (visível externamente).

    [atualize, para elaborar]

    Eu não acho que há qualquer uso para “inline” (sem “estático” ou “extern”) em um arquivo .c. Mas em um arquivo de header faz sentido, e requer uma declaração “extern inline” correspondente em algum arquivo .c para realmente gerar o código independente.

    Do padrão (ISO / IEC 9899: 1999):

    Apêndice J.2 Comportamento Indefinido

    • Uma function com binding externa é declarada com um especificador de function inline , mas também não é definida na mesma unidade de tradução (6.7.4).

    O Comitê C99 escreveu um Fundamentação e diz:

    6.7.4 Especificadores de Função

    Um novo recurso do C99: A palavra-chave inline , adaptada de C ++, é um especificador de function que pode ser usado apenas em declarações de function. É útil para otimizações de programas que exigem que a definição de uma function seja visível no site de uma chamada. (Observe que o padrão não tenta especificar a natureza dessas otimizações).

    A visibilidade é garantida se a function tiver binding interna ou se tiver binding externa e a chamada estiver na mesma unidade de tradução que a definição externa. Nesses casos, a presença da palavra-chave inline em uma declaração ou definição da function não tem efeito além de indicar uma preferência que as chamadas dessa function devem ser otimizadas em preferência a chamadas de outras funções declaradas sem a palavra-chave inline .

    A visibilidade é um problema para uma chamada de uma function com binding externa, em que a chamada está em uma unidade de tradução diferente da definição da function. Nesse caso, a palavra-chave inlineinline permite que a unidade de tradução que contém a chamada também contenha uma definição local ou em linha da function.

    Um programa pode conter uma unidade de tradução com uma definição externa, uma unidade de tradução com uma definição em linha e uma unidade de tradução com uma declaração, mas sem definição para uma function. As chamadas na última unidade de tradução usarão a definição externa como de costume.

    Uma definição em linha de uma function é considerada uma definição diferente da definição externa. Se uma chamada para alguma function func with external linkage ocorre onde uma definição inline é visível, o comportamento é o mesmo que se a chamada fosse feita para outra function, digamos __func , com linkage interno. Um programa em conformidade não deve depender de qual function é chamada. Este é o modelo inline no padrão.

    Um programa em conformidade não deve depender da implementação usando a definição em linha, nem pode depender da implementação usando a definição externa. O endereço de uma function é sempre o endereço correspondente à definição externa, mas quando esse endereço é usado para chamar a function, a definição inline pode ser usada. Portanto, o exemplo a seguir pode não se comportar conforme o esperado.

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

    Como a implementação pode usar a definição inline para uma das chamadas para saddr e usar a definição externa para a outra, a operação de igualdade não é garantida para 1 (true). Isso mostra que objects estáticos definidos dentro da definição inline são distintos de seus objects correspondentes na definição externa. Isso motivou a restrição até mesmo na definição de um object não- const desse tipo.

    Inline foi adicionado ao Standard de tal forma que ele pode ser implementado com a tecnologia de linker existente, e um subconjunto de C99 inlining é compatível com C ++. Isso foi obtido exigindo que exatamente uma unidade de tradução contendo a definição de uma function embutida fosse especificada como aquela que fornece a definição externa para a function. Como essa especificação consiste simplesmente em uma declaração que não possui a palavra-chave inline ou que contém inline e extern , ela também será aceita por um tradutor C ++.

    Inlining no C99 estende a especificação do C ++ de duas maneiras. Primeiro, se uma function é declarada em inline em uma unidade de tradução, ela não precisa ser declarada em inline em todas as outras unidades de tradução. Isso permite, por exemplo, uma function de biblioteca que deve ser embutida na biblioteca, mas disponível somente através de uma definição externa em outro lugar. A alternativa de usar uma function de invólucro para a function externa requer um nome adicional; e também pode afetar negativamente o desempenho se um tradutor não fizer a substituição em linha.

    Segundo, a exigência de que todas as definições de uma function inline sejam “exatamente iguais” é substituída pela exigência de que o comportamento do programa não dependa de uma chamada ser implementada com uma definição inline visível, ou a definição externa, de uma function. Isso permite que uma definição em linha seja especializada para seu uso dentro de uma unidade de tradução específica. Por exemplo, a definição externa de uma function de biblioteca pode include alguma validação de argumento que não é necessária para chamadas feitas de outras funções na mesma biblioteca. Essas extensões oferecem algumas vantagens; e os programadores que estão preocupados com a compatibilidade podem simplesmente obedecer às regras mais rígidas do C ++.

    Observe que não é apropriado para implementações fornecer definições em linha de funções de biblioteca padrão nos headers padrão, pois isso pode quebrar algum código legado que redeclara funções de biblioteca padrão após include seus headers. A palavra-chave inline destina-se apenas a fornecer aos usuários uma maneira portátil de sugerir o inlining de funções. Como os headers padrão não precisam ser portáteis, as implementações têm outras opções ao longo das linhas:

     #define abs(x) __builtin_abs(x) 

    ou outros mecanismos não portáteis para inlining funções de biblioteca padrão.

    > Eu recebo um erro de linker (referência indefinida para f )

    Funciona aqui: Linux x86-64, GCC 4.1.2. Pode ser um bug no seu compilador; Eu não vejo nada no parágrafo citado do padrão que proíbe o programa dado. Observe o uso de if em vez de iff .

    Uma definição inline fornece uma alternativa para uma definição externa , que um tradutor pode usar para implementar qualquer chamada para a function na mesma unidade de tradução.

    Portanto, se você conhece o comportamento da function f e deseja chamá-la em um loop, pode copiar e colar sua definição em um módulo para evitar chamadas de function; ou , você pode fornecer uma definição que, para os propósitos do módulo atual, é equivalente (mas pula a validação de input, ou qualquer otimização que você possa imaginar). O compilador, no entanto, tem a opção de otimizar o tamanho do programa.