Código compilado mora na memória assim como os dados, então uma função tem um endereço. Um ponteiro para função é uma variável que guarda um desses endereços - e uma vez que você consegue guardar uma função, pode passá-la como argumento, manter uma tabela delas, ou escolher entre elas em tempo de execução.
É essa a capacidade por trás de ordenar com uma ordem personalizada, de tratadores de eventos, de interfaces de plugin e de máquinas de estados. Também é a parte da sintaxe de C mais propensa a fazer um leitor parar e apertar os olhos, então começamos por ela.
A sintaxe da declaração
Pegue uma função comum:
int add(int a, int b);
Para declarar um ponteiro que pode guardar o endereço dela, troque o nome por (*nome):
int (*op)(int, int);
Leia de dentro para fora: op é um ponteiro, para uma função, que recebe (int, int), retornando int.
Os parênteses não são decoração. Sem eles, o * se liga ao tipo de retorno:
int (*f)(int); // ponteiro para uma função que recebe int e retorna int
int *g(int); // FUNÇÃO que recebe int e retorna int* - completamente diferente
Aqui está ele funcionando de ponta a ponta:
As duas formas de chamada funcionam porque um designador de função converte-se automaticamente para um ponteiro. Escreva op(10, 3); a grafia (*op)(10, 3) é um resquício.
Uma observação sobre aquele último printf: %p espera um ponteiro para objeto, e ponteiros para função são formalmente uma família separada, então imprimir um não é estritamente portável. O cast mantém os compiladores quietos nas plataformas em que aquilo funciona; você raramente vai precisar imprimir um.
typedef torna isso legível
A sintaxe de declaração fica feia rápido, e typedef é o remédio padrão:
Compare int apply(BinaryOp op, int x, int y) com a forma crua, int apply(int (*op)(int, int), int x, int y). Mesmo significado; uma delas você consegue ler num relance. Veja typedef para as regras gerais.
Callbacks: deixar quem chama fornecer o comportamento
Um callback é uma função que você entrega a outra função para que ela possa chamá-lo de volta no momento certo. Isso deixa um pedaço de código cuidar da estrutura de um trabalho enquanto quem chama fornece a decisão.
Sem ponteiros para função você escreveria count_even, count_positive e count_big - três cópias do mesmo laço. Com eles, o laço é escrito uma vez e o teste é o parâmetro.
A recompensa de verdade: qsort
O qsort da biblioteca padrão é o exemplo de ponteiro para função que você realmente vai usar. Ele ordena um array de qualquer coisa, e consegue isso sem saber nada sobre os elementos além do tamanho deles e de um comparador que você fornece.
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
O comparador recebe dois const void * - os endereços de dois elementos - e retorna um número negativo se o primeiro vem antes do segundo, zero se empatam, e positivo se vem depois.
Dois hábitos para levar desse comparador. Nunca escreva return x - y; - parece esperto e transborda para valores grandes ou negativos, produzindo um sinal errado e um array mal ordenado. Faça o cast antes de desreferenciar, não depois: *(const int *)a, porque *a em um void * não tem nada para ler.
A mesma maquinaria ordena structs por qualquer campo que você quiser:
A expressão idiomática (q->score > p->score) - (q->score < p->score) retorna exatamente -1, 0 ou 1, sem subtração e sem overflow. E strcmp já tem a convenção de retorno certa, então um comparador de strings é uma linha só.
Tabelas de ponteiros para função
Um array de ponteiros para função transforma uma cadeia de if/else em uma consulta. É assim que interpretadores despacham opcodes e que sistemas de menu executam comandos.
Acrescentar uma operação agora é acrescentar uma linha, não editar um switch. Junte isso com structs e você tem o formato padrão de plugin em C: uma struct de ponteiros para função é o que significa "uma interface" nesta linguagem.
Coisas que mordem
As assinaturas precisam coincidir exatamente. O tipo de um ponteiro para função inclui os tipos dos parâmetros e o tipo de retorno. Atribuir uma função incompatível é uma violação de restrição, e chamar através de um ponteiro com o tipo errado é comportamento indefinido mesmo quando "funciona". Não faça cast da assinatura de um comparador para calar um aviso - conserte o comparador.
Um ponteiro para função nulo continua sendo um ponteiro nulo. Inicialize com NULL e verifique antes de chamar, exatamente como com ponteiros de dados:
if (handler != NULL) {
handler(event);
}
Veja ponteiros nulos para entender por que essa verificação vale o esforço.
Ponteiros void perdem a segurança de tipos, e o sistema de tipos é você. Dentro de um comparador do qsort, nada impede que você faça cast para o tipo errado. Se você ordenar um array de double com compare_ints, isso compila limpo e produz besteira.
A sintaxe de declaradores aninha. Uma função que retorna um ponteiro para função é int (*get_op(char c))(int, int);. Quando você se pegar escrevendo isso, recorra ao typedef:
typedef int (*BinaryOp)(int, int);
BinaryOp get_op(char c); // a mesma coisa, legível
Perguntas frequentes
Como se declara um ponteiro para função em C?
Escreva a assinatura da função com (*nome) onde ficaria o nome da função: int (*op)(int, int); declara op como um ponteiro para uma função que recebe dois ints e retorna int. Os parênteses em volta de *op são obrigatórios - sem eles você declara uma função que retorna um ponteiro.
Como se chama uma função através de um ponteiro?
Ou op(3, 4) ou (*op)(3, 4) - as duas formas são legais e fazem a mesma coisa, porque um designador de função converte-se automaticamente para um ponteiro. Código C moderno usa a forma simples op(3, 4).
O que é um callback em C?
Uma função que você entrega a outra função para que ela possa chamar você de volta. qsort é o exemplo clássico: você passa um comparador, e o qsort o chama sempre que precisa saber qual de dois elementos vem primeiro. É isso que faz uma única rotina de ordenação funcionar com qualquer tipo.
É preciso usar o & ao pegar o endereço de uma função?
Não. Um nome de função já se converte em um ponteiro para aquela função, então op = add; e op = &add; são equivalentes. A maior parte do código C omite o &.