Funções inline e macros de função são mecanismos que permitem ao programador sugerir ao compilador que evite a chamada de função tradicional, inserindo o código diretamente no ponto de chamada. Embora ambos possam melhorar o desempenho em certos cenários, eles têm diferenças fundamentais em termos de segurança, escopo e comportamento. Nesta aula, vamos explorar detalhadamente cada um desses recursos, seus prós e contras, e as melhores situações para empregá-los.

É importante entender que, enquanto as macros são processadas pelo pré-processador (uma etapa textual anterior à compilação), as funções inline são tratadas pelo compilador, que decide se realmente irá inline ou não. Essa distinção traz implicações significativas para a correção e manutenção do código.

inline

O qualificador inline é uma dica para o compilador de que a função deve ser expandida inline, ou seja, seu corpo deve ser inserido no local da chamada, evitando a sobrecarga de chamada de função (push/pop de argumentos, desvio, etc.). No entanto, o compilador pode ignorar essa sugestão se julgar que a expansão não traz benefícios ou é inviável.

Uma função inline é declarada com a palavra-chave inline antes do tipo de retorno. Para que o compilador consiga expandi-la, a definição deve estar visível no ponto de chamada (geralmente no mesmo arquivo ou em um cabeçalho). Exemplo:

#include <stdio.h>

inline int quadrado(int x) {
    return x * x;
}

int main() {
    int a = 5;
    printf("Quadrado de %d é %d\n", a, quadrado(a));
    return 0;
}

Funções inline são adequadas para funções pequenas e chamadas frequentemente, como operações matemáticas simples. Elas preservam a segurança de tipo e o escopo de variáveis, ao contrário das macros. Além disso, o compilador pode realizar otimizações adicionais, como eliminação de código morto.

Macros com argumentos

Macros com argumentos são definidas com #define e podem receber parâmetros, que são substituídos textualmente no código. Exemplo:

#include <stdio.h>

#define QUADRADO(x) ((x) * (x))

int main() {
    int a = 5;
    printf("Quadrado de %d é %d\n", a, QUADRADO(a));
    return 0;
}

Note os parênteses extras em torno de (x) e de toda a expressão: isso é essencial para evitar problemas de precedência. Por exemplo, QUADRADO(a+1) sem os parênteses internos seria expandido para a+1*a+1, resultando em a + a + 1 (devido à precedência de multiplicação), o que está errado. Com os parênteses, a expansão correta é ((a+1)*(a+1)).

Macros podem ser úteis para operações que não podem ser expressas como funções, como a criação de constantes com parâmetros ou a geração de código. No entanto, elas têm várias desvantagens.

Riscos de macros

O maior risco das macros é a substituição textual cega, que pode causar efeitos colaterais inesperados. Por exemplo, se você passar uma expressão com incremento como argumento, como QUADRADO(++a), a macro expandirá para ((++a) * (++a)), incrementando a duas vezes, o que é indefinido. Além disso, macros não respeitam escopo, podem sombrear variáveis e são difíceis de depurar.

Outro problema é a falta de verificação de tipos: a macro aceita qualquer tipo, e erros de tipo só aparecem na compilação do código expandido, muitas vezes com mensagens confusas. Macros também podem causar poluição do namespace e não podem ser usadas como ponteiros de função.

Exemplo de problema:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int x = 5, y = 10;
int maior = MAX(x++, y++);
// Expande para ((x++) > (y++) ? (x++) : (y++))
// Isso incrementa x ou y duas vezes, comportamento indefinido.

Quando usar cada um

Em geral, prefira funções inline a macros sempre que possível. Use inline para funções pequenas e de chamada frequente, onde o ganho de desempenho é relevante e a segurança de tipo é importante. Use macros apenas quando não houver alternativa, como em:

  • Definição de constantes com parâmetros (ex.: #define TAMANHO_BUFFER 256)
  • Geração de código repetitivo (ex.: macros de logging que usam __FILE__ e __LINE__)
  • Operações que exigem contexto do pré-processador (ex.: #ifdef)

Para funções que precisam ser usadas em diferentes tipos, considere _Generic (C11) ou funções inline com tipos específicos. Lembre-se de que o compilador moderno é inteligente: muitas vezes ele inline automaticamente funções pequenas sem a necessidade do inline explícito.

Boas práticas

  • Em macros, sempre coloque parênteses em volta dos argumentos e da expressão inteira.
  • Evite usar argumentos com efeitos colaterais (como incrementos) em macros.
  • Prefira funções inline para código que será reutilizado e precisa de verificação de tipos.
  • Não abuse de inline: use apenas quando realmente houver necessidade de desempenho.
  • Documente macros complexas para facilitar a manutenção.

Referências

Exercícios

  1. Escreva uma função inline que calcule o cubo de um número inteiro.

    ✓ Resposta:
    inline int cubo(int x) {
        return x * x * x;
    }
  2. Defina uma macro que calcule o mínimo entre dois valores. Considere os riscos.

    ✓ Resposta:
    #define MIN(a, b) ((a) < (b) ? (a) : (b))

    Risco: se argumentos tiverem efeitos colaterais, eles podem ser avaliados duas vezes.

  3. Explique por que o código abaixo pode gerar um resultado incorreto e corrija-o usando uma função inline.

    #define DOBRO(x) x + x
    
    int main() {
        int a = 5;
        int b = DOBRO(a) * 3;
        printf("%d\n", b);
        return 0;
    }

    ✓ Resposta:

    A macro expande para a + a * 3, que é 5 + 15 = 20 (devido à precedência), mas o esperado seria (5+5)*3=30. Corrigindo com função inline:

    inline int dobro(int x) {
        return x + x;
    }
  4. Escreva um programa que use uma macro para trocar os valores de duas variáveis inteiras (swap).

    ✓ Resposta:
    #include 
    
    #define SWAP(a, b) do { int temp = a; a = b; b = temp; } while(0)
    
    int main() {
        int x = 10, y = 20;
        SWAP(x, y);
        printf("x=%d y=%d\n", x, y);
        return 0;
    }
  5. Converta a macro do exercício 2 em uma função inline segura. Compare as vantagens.

    ✓ Resposta:
    inline int min(int a, int b) {
        return a < b ? a : b;
    }

    Vantagens: verificação de tipo, sem dupla avaliação de argumentos, melhor depuração.