Função inline - Inline function

Nas linguagens de programação C e C ++ , uma função embutida é aquela qualificada com a palavra - chave ; isso serve a dois propósitos. Em primeiro lugar, ele serve como uma diretiva do compilador que sugere (mas não exige) que o compilador substitua o corpo da função inline executando a expansão inline , ou seja, inserindo o código da função no endereço de cada chamada de função, economizando assim a sobrecarga de uma chamada de função. Nesse aspecto, é análogo ao especificador de classe de armazenamento , que fornece uma dica de otimização da mesma forma. O segundo objetivo é mudar o comportamento da ligação; os detalhes disso são complicados. Isso é necessário devido ao modelo de compilação + ligação separada C / C ++, especificamente porque a definição (corpo) da função deve ser duplicada em todas as unidades de tradução onde é usada, para permitir inlining durante a compilação , que, se a função tiver ligação , causa uma colisão durante a ligação (viola a exclusividade de símbolos externos). C e C ++ (e dialetos como GNU C e Visual C ++) resolvem isso de maneiras diferentes. inlineregister inline

Exemplo

Uma inlinefunção pode ser escrita em C ou C ++ assim:

inline void swap(int *m, int *n)
{
    int tmp = *m;
    *m = *n;
    *n = tmp;
}

Em seguida, uma declaração como a seguinte:

swap(&x, &y);

pode ser traduzido para (se o compilador decidir fazer o inlining, que normalmente requer que a otimização seja ativada):

int tmp = x;
x = y;
y = tmp;

Ao implementar um algoritmo de classificação fazendo muitas trocas, isso pode aumentar a velocidade de execução.

Suporte padrão

C ++ e C99 , mas não seus predecessores K&R C e C89 , têm suporte para inlinefunções, embora com semânticas diferentes. Em ambos os casos, inlinenão força o inlining; o compilador é livre para escolher não embutir a função de forma alguma, ou apenas em alguns casos. Diferentes compiladores variam em quão complexa é uma função que eles podem gerenciar para embutir. Compiladores C ++ convencionais, como Microsoft Visual C ++ e GCC, oferecem suporte a uma opção que permite aos compiladores embutir automaticamente qualquer função adequada, mesmo aquelas não marcadas como inlinefunções. No entanto, simplesmente omitir a inlinepalavra-chave para permitir que o compilador tome todas as decisões in-line não é possível, uma vez que o vinculador irá reclamar sobre definições duplicadas em diferentes unidades de tradução. Isso ocorre porque inlinenão apenas dá ao compilador uma dica de que a função deve ser embutida, mas também tem um efeito sobre se o compilador irá gerar uma cópia fora de linha que pode ser chamada da função (consulte classes de armazenamento de funções embutidas ).

Extensões fora do padrão

GNU C , como parte do dialeto gnu89 que oferece, tem suporte para inlinecomo uma extensão para C89. No entanto, a semântica difere de C ++ e C99. armcc no modo C90 também oferece inlineuma extensão não padrão, com semântica diferente de gnu89 e C99.

Algumas implementações fornecem um meio pelo qual forçar o compilador a embutir uma função, geralmente por meio de especificadores de declaração específicos de implementação:

  • Microsoft Visual C ++: __forceinline
  • gcc ou clang: __attribute__((always_inline))ou __attribute__((__always_inline__)), o último dos quais é útil para evitar um conflito com uma macro definida pelo usuário chamada always_inline.

O uso indiscriminado disso pode resultar em código maior (arquivo executável inchado), ganho mínimo ou nenhum ganho de desempenho e, em alguns casos, até mesmo perda de desempenho. Além disso, o compilador não pode embutir a função em todas as circunstâncias, mesmo quando o embutir é forçado; neste caso, o gcc e o Visual C ++ geram avisos.

Forçar o inlining é útil se

  • inline não é respeitado pelo compilador (ignorado pelo analisador de custo / benefício do compilador), e
  • inlining resulta em um aumento de desempenho necessário

Para portabilidade do código, as seguintes diretivas de pré-processador podem ser usadas:

#ifdef _MSC_VER
    #define forceinline __forceinline
#elif defined(__GNUC__)
    #define forceinline inline __attribute__((__always_inline__))
#elif defined(__CLANG__)
    #if __has_attribute(__always_inline__)
        #define forceinline inline __attribute__((__always_inline__))
    #else
        #define forceinline inline
    #endif
#else
    #define forceinline inline
#endif

Classes de armazenamento de funções inline

static inlinetem os mesmos efeitos em todos os dialetos C e C ++. Ele emitirá uma função visível localmente (cópia fora de linha de), se necessário.

Independentemente da classe de armazenamento, o compilador pode ignorar o inlinequalificador e gerar uma chamada de função em todos os dialetos C e C ++.

O efeito da classe de armazenamento externquando aplicada ou não aplicada às inlinefunções difere entre os dialetos C e C ++.

C99

No C99, uma função definida inlinenunca irá, e uma função definida extern inlineirá sempre, emitir uma função externamente visível. Ao contrário do C ++, não há como solicitar que uma função externamente visível compartilhada entre as unidades de tradução seja emitida apenas se necessário.

Se as inlinedeclarações forem misturadas com extern inlinedeclarações ou com declarações não qualificadas (ou seja, sem inlinequalificador ou classe de armazenamento), a unidade de tradução deve conter uma definição (não importa se não qualificada inline, ou extern inline) e uma função visível externamente será emitida para ela.

Uma função definida inlinerequer exatamente uma função com aquele nome em algum outro lugar do programa que está definido extern inlineou sem qualificador. Se mais de uma definição for fornecida em todo o programa, o vinculador reclamará sobre símbolos duplicados. Se, no entanto, estiver faltando, o vinculador não necessariamente reclamará, porque, se todos os usos pudessem ser sequenciais, isso não seria necessário. Mas pode reclamar, já que o compilador sempre pode ignorar o inlinequalificador e gerar chamadas para a função, como normalmente acontece se o código for compilado sem otimização. (Este pode ser o comportamento desejado, se a função deve estar embutida em todos os lugares por todos os meios, e um erro deve ser gerado se não estiver.) Uma maneira conveniente é definir as inlinefunções em arquivos de cabeçalho e criar um arquivo .c por função, contendo uma extern inlinedeclaração para ela e incluindo o respectivo arquivo de cabeçalho com a definição. Não importa se a declaração é anterior ou posterior à inclusão.

Para evitar que código inacessível seja adicionado ao executável final se todos os usos de uma função forem embutidos, é aconselhável colocar os arquivos objeto de todos esses arquivos .c com uma única extern inlinefunção em um arquivo de biblioteca estática , normalmente com ar rcs, e vincular contra essa biblioteca em vez dos arquivos de objeto individuais. Isso faz com que sejam vinculados apenas os arquivos-objeto realmente necessários, em contraste com a vinculação direta dos arquivos-objeto, que faz com que sejam sempre incluídos no executável. No entanto, o arquivo de biblioteca deve ser especificado depois de todos os outros arquivos de objeto na linha de comando do vinculador, uma vez que as chamadas de arquivos de objeto especificados após o arquivo de biblioteca para as funções não serão consideradas pelo vinculador. As chamadas de inlinefunções para outras inlinefunções serão resolvidas pelo vinculador automaticamente (a sopção em ar rcsgarante isso).

Uma solução alternativa é usar a otimização de tempo de link em vez de uma biblioteca. gcc fornece o sinalizador -Wl,--gc-sectionspara omitir seções nas quais todas as funções não são utilizadas. Esse será o caso para arquivos-objeto que contêm o código de uma única extern inlinefunção não utilizada . No entanto, ele também remove todas e quaisquer outras seções não utilizadas de todos os outros arquivos de objeto, não apenas aquelas relacionadas a extern inlinefunções não utilizadas . (Pode ser desejável vincular funções ao executável que devem ser chamadas pelo programador a partir do depurador em vez do próprio programa, por exemplo, para examinar o estado interno do programa.) Com esta abordagem, também é possível para usar um único arquivo .c com todas as extern inlinefunções em vez de um arquivo .c por função. Em seguida, o arquivo deve ser compilado -fdata-sections -ffunction-sections. No entanto, a página de manual do gcc avisa sobre isso, dizendo "Use essas opções apenas quando houver benefícios significativos em fazê-lo."

Alguns recomendam uma abordagem totalmente diferente, que é definir funções como em static inlinevez de inlinearquivos de cabeçalho. Então, nenhum código inacessível será gerado. No entanto, essa abordagem tem uma desvantagem no caso oposto: o código duplicado será gerado se a função não puder ser embutida em mais de uma unidade de tradução. O código de função emitido não pode ser compartilhado entre as unidades de tradução porque deve ter endereços diferentes. Esta é outra desvantagem; tomar o endereço de uma função definida como static inlineem um arquivo de cabeçalho produzirá valores diferentes em unidades de tradução diferentes. Portanto, as static inlinefunções só devem ser usadas se forem usadas em apenas uma unidade de tradução, o que significa que devem ir apenas para o respectivo arquivo .c, e não para um arquivo de cabeçalho.

gnu89

semântica gnu89 de inlinee extern inlinesão essencialmente o oposto exato daquelas em C99, com a exceção de que gnu89 permite a redefinição de uma extern inlinefunção como uma função não qualificada, enquanto C99 inlinenão permite. Assim, gnu89 extern inlinesem redefinição é como C99 inline, e gnu89 inlineé como C99 extern inline; em outras palavras, no gnu89, uma função definida inlinesempre e uma função definida extern inlinenunca emitirá uma função visível externamente. A justificativa para isso é que ele corresponde a variáveis, para as quais o armazenamento nunca será reservado se definido como externe sempre será definido sem. A justificativa para C99, em contraste, é que seria surpreendente se o uso inlinetivesse um efeito colateral - sempre emitir uma versão não embutida da função - que é contrário ao que seu nome sugere.

As observações para C99 sobre a necessidade de fornecer exatamente uma instância de função visível externamente para funções embutidas e sobre o problema resultante com código inacessível se aplicam mutatis mutandis a gnu89 também.

O gcc até a versão 4.2, inclusive, usava a inlinesemântica do gnu89 mesmo quando -std=c99era explicitamente especificada. Com a versão 5, o gcc mudou de gnu89 para o dialeto gnu11, ativando efetivamente a inlinesemântica C99 por padrão. Para usar a semântica do gnu89, eles devem ser ativados explicitamente, com -std=gnu89ou, para afetar apenas o inlining -fgnu89-inline, ou adicionando o gnu_inlineatributo a todas as inlinedeclarações. Para assegurar semântica C99, ou -std=c99, -std=c11, -std=gnu99ou -std=gnu11(sem -fgnu89-inline) pode ser utilizado.

C ++

Em C ++, uma função definida inlineemitirá, se necessário, uma função compartilhada entre as unidades de tradução, normalmente colocando-a na seção comum do arquivo-objeto para o qual é necessária. A função deve ter a mesma definição em todos os lugares, sempre com o inlinequalificador. Em C ++, extern inlineé o mesmo que inline. A justificativa para a abordagem C ++ é que é a maneira mais conveniente para o programador, uma vez que nenhum cuidado especial para eliminação de código inacessível deve ser tomado e, como para funções comuns, não faz diferença se externé especificado ou não.

O inlinequalificador é adicionado automaticamente a uma função definida como parte de uma definição de classe.

armcc

armcc no modo C90 proporciona extern inlinee inlinesemântica que são os mesmos que no C ++: Tais definições irá emitir uma função compartilhada entre as unidades de tradução, se necessário. No modo C99, extern inlinesempre emite uma função, mas como no C ++, ela será compartilhada entre as unidades de tradução. Assim, a mesma função pode ser definida extern inlineem diferentes unidades de tradução. Isso corresponde ao comportamento tradicional dos compiladores Unix C para várias não- externdefinições de variáveis ​​globais não inicializadas.

Restrições

Obter o endereço de uma inlinefunção requer código para que uma cópia não embutida dessa função seja emitida em qualquer caso.

No C99, uma função inlineou extern inlinenão deve acessar staticvariáveis ​​globais ou definir const staticvariáveis não locais. const staticvariáveis ​​locais podem ou não ser objetos diferentes em unidades de tradução diferentes, dependendo se a função estava embutida ou se uma chamada foi feita. Apenas as static inlinedefinições podem fazer referência a identificadores com ligação interna sem restrições; esses serão objetos diferentes em cada unidade de tradução. Em C ++, tanto os constnão const staticlocais quanto os não locais são permitidos e se referem ao mesmo objeto em todas as unidades de tradução.

gcc não pode funções embutidas se

  1. eles são variados ,
  2. usar alloca
  3. usar computado goto
  4. usar não local goto
  5. usar funções aninhadas
  6. usar setjmp
  7. usar __builtin_longjmp
  8. usar __builtin_return, ou
  9. usar __builtin_apply_args

Com base nas especificações da Microsoft no MSDN, o MS Visual C ++ não pode embutir (nem mesmo com __forceinline), se

  1. A função ou seu chamador é compilado com / Ob0 (a opção padrão para compilações de depuração).
  2. A função e o chamador usam diferentes tipos de tratamento de exceção ( tratamento de exceção C ++ em um, tratamento de exceção estruturado no outro).
  3. A função possui uma lista de argumentos variáveis .
  4. A função usa assembly embutido , a menos que compilado com / Og, / Ox, / O1 ou / O2.
  5. A função é recursiva e não acompanhada por #pragma inline_recursion(on). Com o pragma, as funções recursivas são alinhadas a uma profundidade padrão de 16 chamadas. Para reduzir a profundidade do inlining, use o inline_depthpragma.
  6. A função é virtual e é chamada virtualmente. As chamadas diretas para funções virtuais podem ser embutidas.
  7. O programa obtém o endereço da função e a chamada é feita por meio do ponteiro para a função. As chamadas diretas para funções cujos endereços foram atendidos podem ser sequenciais.
  8. A função também é marcada com o __declspecmodificador nu .

Problemas

Além dos problemas com a expansão inline em geral (consulte Expansão inline § Efeito no desempenho ), as inlinefunções como um recurso de linguagem podem não ser tão valiosas quanto parecem, por uma série de razões:

  • Freqüentemente, um compilador está em uma posição melhor do que um humano para decidir se uma função específica deve ser embutida. Às vezes, o compilador pode não ser capaz de embutir tantas funções quanto o programador indica.
  • Um ponto importante a notar é que o código (da inlinefunção) fica exposto ao seu cliente (a função de chamada).
  • Conforme as funções evoluem, eles podem se tornar adequados para inlining onde antes, ou não mais adequados para inlining onde estavam. Embora inlining ou un-inlining uma função seja mais fácil do que converter de e para macros, ainda requer manutenção extra que normalmente produz relativamente poucos benefícios.
  • As funções inline usadas na proliferação em sistemas de compilação baseados em C nativos podem aumentar o tempo de compilação, uma vez que a representação intermediária de seus corpos é copiada em cada site de chamada.
  • A especificação inlineem C99 requer exatamente uma definição externa da função, se for usada em algum lugar. Se tal definição não foi fornecida pelo programador, isso pode facilmente levar a erros do vinculador. Isso pode acontecer com a otimização desativada, o que normalmente impede o inlining. Adicionar as definições, por outro lado, pode causar código inacessível se o programador não o evita cuidadosamente, colocando-as em uma biblioteca para vinculação, usando a otimização de tempo de link ou static inline.
  • Em C ++, é necessário definir uma inlinefunção em cada módulo (unidade de tradução) que a utiliza, enquanto uma função comum deve ser definida em apenas um único módulo. Caso contrário, não seria possível compilar um único módulo independentemente de todos os outros módulos. Dependendo do compilador, isso pode fazer com que cada arquivo de objeto respectivo contenha uma cópia do código da função, para cada módulo com algum uso que não poderia ser embutido.
  • Em software embarcado , muitas vezes certas funções precisam ser colocadas em certas seções de código pelo uso de instruções especiais do compilador, como instruções "pragma". Às vezes, uma função em um segmento de memória pode precisar chamar uma função em outro segmento de memória e, se ocorrer inlining da função chamada, o código da função chamada pode terminar em um segmento onde não deveria estar. Por exemplo, segmentos de memória de alto desempenho podem ser muito limitados no espaço de código, e se uma função pertencente a tal espaço chamar outra grande função que não se destina a estar na seção de alto desempenho e a função chamada ficar inadequadamente alinhada, então isso pode fazer com que o segmento de memória de alto desempenho fique sem espaço de código. Por esse motivo, às vezes é necessário garantir que as funções não fiquem embutidas.

Citações

"Uma declaração de função [...] Com um especificador embutido declara uma função embutida. O especificador embutido indica para a implementação que a substituição embutida do corpo da função no ponto de chamada deve ser preferida ao mecanismo de chamada de função usual. Uma implementação não é necessário realizar esta substituição em linha no ponto de chamada; no entanto, mesmo que esta substituição em linha seja omitida, as outras regras para funções em linha definidas em 7.1.2 ainda devem ser respeitadas. "
- ISO / IEC 14882: 2011, o padrão C ++ atual, seção 7.1.2
"Uma função declarada com um especificador de função embutida é uma função embutida. [...] Tornar uma função uma função embutida sugere que as chamadas para a função sejam o mais rápidas possível. A extensão em que essas sugestões são eficazes é definida pela implementação ( nota de rodapé: por exemplo, uma implementação pode nunca realizar substituições in-line ou pode realizar apenas substituições in-line para chamadas no escopo de uma declaração in-line. )
"[...] Uma definição embutida não fornece uma definição externa para a função e não proíbe uma definição externa em outra unidade de tradução . Uma definição embutida fornece uma alternativa para uma definição externa, que um tradutor pode usar para implementar qualquer chamada para a função na mesma unidade de tradução. Não é especificado se uma chamada para a função usa a definição embutida ou a definição externa. "
- ISO 9899: 1999 (E), o padrão C99, seção 6.7.4

Veja também

Referências

links externos