C Ponteiros para Funções

Você já sabe que variáveis moram em endereços. As funções também: as instruções de cada função ficam guardadas na memória do programa, e o nome da função é, no fundo, o endereço onde elas começam. Um ponteiro para função guarda esse endereço, e com ele você pode escolher qual função chamar enquanto o programa roda: a operação de uma calculadora, a ação de um item de menu, o critério de uma ordenação. É assim que o qsort da biblioteca padrão ordena qualquer coisa.

Você vai aprender
  • Como declarar um ponteiro para função (int (*op)(int, int)) e por que os parênteses são obrigatórios.
  • Como guardar uma função nele e chamá-la: op = somar; e op(6, 3).
  • Como escolher a operação de uma calculadora durante a execução.
  • Como passar uma função para outra função (o callback), como faz o qsort.
  • Como montar um menu com um array de ponteiros de função.
  • Como o typedef deixa esses tipos legíveis.
  • Os erros reais: tipo de função incompatível, parênteses esquecidos, chamar no lugar de apontar e chamar um ponteiro nulo.

Esta aula supõe que você já leu Funções e Ponteiros. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: o botão programável

O controle remoto da TV da Ana tem um botão com uma estrela, o "favorito". Ele não faz nada de fábrica: a Ana escolhe o que ele faz. Hoje ele abre o canal de esportes; amanhã ela reprograma e ele passa a abrir o aplicativo de música. O botão é sempre o mesmo, mas a ação guardada nele muda.

Um ponteiro para função é esse botão. Você guarda nele "qual função rodar" e, quando aperta (chama o ponteiro), roda a função que estiver guardada naquele momento.

No controle remotoEm C
As ações possíveis (canal de esportes, música…)As funções: somar, multiplicar…
O botão favoritoO ponteiro: op
Programar o botãoop = somar;
Apertar o botãoop(6, 3)
O botão só aceita ações "de TV"O ponteiro só aceita funções com os mesmos parâmetros e o mesmo retorno

1. Declarar, guardar e chamar

#include <stdio.h>

int somar(int a, int b) {
    return a + b;
}

int multiplicar(int a, int b) {
    return a * b;
}

int main(void) {
    int (*op)(int, int);

    op = somar;
    printf("op(6, 3) = %d\n", op(6, 3));

    op = multiplicar;
    printf("op(6, 3) = %d\n", op(6, 3));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pf1.c -o pf1
$ ./pf1
op(6, 3) = 9             « op guardava somar
op(6, 3) = 18            « a mesma linha, agora com multiplicar

A mesma chamada op(6, 3) deu dois resultados, porque a função guardada em op mudou entre uma e outra. Agora, a parte que assusta todo mundo, a declaração:

int(*op)(int, int);
o que a função devolveop é um ponteiroos parâmetros que a função recebefim
Leia de dentro para fora, começando pelo nome: "op é um ponteiro (*op) para uma função que recebe dois int ((int, int)) e devolve int".
int (*op)(int, int);
É a mesma "forma" do protótipo int somar(int a, int b);, com (*op) no lugar do nome. Os nomes dos parâmetros podem ficar de fora.
(*op)
Os parênteses são obrigatórios. Sem eles, int *op(int, int); declara uma função chamada op que devolve int *. É o erro mais comum deste assunto (veja "Quando dá errado").
op = somar;
O nome de uma função, sem parênteses, é o endereço dela. Com parênteses (somar(6, 3)) você estaria chamando a função e guardando o resultado, 9, o que não é um endereço.
op(6, 3)
Chama a função para onde op aponta, como se op fosse o nome dela.
Escritas equivalentes

op = somar; e op = &somar; fazem o mesmo; op(6, 3) e (*op)(6, 3) também. Código antigo usa muito o (*op)(…) para deixar claro que op é um ponteiro. Quer uma prova de que &somar e somar são a mesma coisa? Compare os dois com ==, e o próprio gcc avisa: warning: self-comparison always evaluates to true ("comparar uma coisa consigo mesma sempre dá verdadeiro").

Onde as funções moram

Para ver os endereços, dá para mostrar a função com %p, convertendo para (void *):

#include <stdio.h>

int somar(int a, int b)       { return a + b; }
int multiplicar(int a, int b) { return a * b; }

int main(void) {
    int (*op)(int, int) = somar;
    printf("somar       mora em %p\n", (void *)somar);
    printf("multiplicar mora em %p\n", (void *)multiplicar);
    printf("op guarda           %p\n", (void *)op);
    printf("op mora em          %p\n", (void *)&op);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ondemora.c -o ondemora
$ ./ondemora
somar       mora em 0x56f0e9d1c169       « na região do código do programa
multiplicar mora em 0x56f0e9d1c181       « 0x18 (24) bytes depois: o tamanho das instruções de somar
op guarda           0x56f0e9d1c169       « o endereço de somar
op mora em          0x7ffdaf0af500       « o próprio op é uma variável local, na pilha
pilha (variáveis locais) op int (*)(int, int) 0x56f0e9d1c169 mora em …af500 código do programa (instruções) somar …c169: return a + b; multiplicar …c181: return a * b;
O ponteiro para função é uma variável como outra qualquer (mora na pilha), mas o endereço guardado nele aponta para a região do código, onde ficam as instruções das funções. A aula Pilha e Heap mostra o mapa completo.

Dois detalhes honestos sobre esse programa. Primeiro, o padrão do C não garante que um endereço de função caiba num void *; no Linux cabe, mas com -pedantic o gcc avisa: ISO C forbids conversion of function pointer to object pointer type ("o C padrão proíbe converter ponteiro de função em ponteiro de dado"). Use isso só para curiosidade. Segundo, no editor do guia a saída é bem diferente: somar mora em 0x1, multiplicar mora em 0x2. No WebAssembly, as funções ficam numa tabela separada da memória e o "endereço" é só a posição nessa tabela. A ideia continua a mesma: um número que identifica a função.

Experimente: rode. Depois crie uma função int maior(int a, int b) que devolve o maior dos dois números, guarde-a em op e mostre op(6, 3) mais uma vez.

#include <stdio.h>

int somar(int a, int b) {
    return a + b;
}

int multiplicar(int a, int b) {
    return a * b;
}

int main(void) {
    int (*op)(int, int);

    op = somar;
    printf("op(6, 3) = %d\n", op(6, 3));

    op = multiplicar;
    printf("op(6, 3) = %d\n", op(6, 3));
    return 0;
}

2. Escolher a operação: uma calculadora

A vantagem aparece quando a função é escolhida durante a execução, a partir do que o usuário digitou. A calculadora da Ana lê uma conta como 6 * 3, escolhe a função uma vez e depois chama op sem precisar saber qual é:

#include <stdio.h>

int somar(int a, int b)       { return a + b; }
int subtrair(int a, int b)    { return a - b; }
int multiplicar(int a, int b) { return a * b; }

int main(void) {
    int a = 0, b = 0;
    char sinal = ' ';
    printf("Conta (ex.: 6 + 3): ");
    if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
        printf("Não entendi a conta.\n");
        return 1;
    }

    int (*op)(int, int) = NULL;
    if (sinal == '+') {
        op = somar;
    } else if (sinal == '-') {
        op = subtrair;
    } else if (sinal == '*') {
        op = multiplicar;
    }

    if (op == NULL) {
        printf("Não conheço a operação '%c'.\n", sinal);
        return 1;
    }
    printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
    return 0;
}
scanf("%d %c %d", …)
Lê um número, um caractere (o sinal) e outro número. O espaço antes do %c pula os espaços digitados. Conferir o retorno (3 itens lidos) é a regra do guia (aula Entrada com scanf).
int (*op)(int, int) = NULL;
O ponteiro começa nulo: "nenhuma função escolhida ainda". Toda variável nasce com valor, e ponteiros também.
op = somar;
Cada if só escolhe; ninguém faz conta ainda.
if (op == NULL)
Se o sinal não era conhecido, op continua nulo. Chamar um ponteiro nulo derruba o programa, então a conferência é obrigatória.
op(a, b)
Uma linha só faz a conta, qualquer que seja a operação.
$ gcc -std=c23 -Wall -Wextra calc.c -o calc
$ ./calc
Conta (ex.: 6 + 3): 6 * 3
6 * 3 = 18
$ ./calc
Conta (ex.: 6 + 3): 10 - 4
10 - 4 = 6
$ ./calc
Conta (ex.: 6 + 3): 8 / 2
Não conheço a operação '/'.

"Mas um if que já fizesse a conta não daria no mesmo?" Neste programa pequeno, quase. A diferença aparece quando a escolha é feita num lugar e o uso em outro: a função escolhida pode ser guardada, passada para outras funções, colocada num array. As próximas seções mostram isso.

Missão: ensine a calculadora a dividir. Crie a função int dividir(int a, int b) (com return a / b;) e acrescente um else if que a escolha quando o sinal for '/'. A entrada 8 / 2 já está preparada; a saída deve terminar com 8 / 2 = 4.

#include <stdio.h>

int somar(int a, int b)       { return a + b; }
int subtrair(int a, int b)    { return a - b; }
int multiplicar(int a, int b) { return a * b; }

int main(void) {
    int a = 0, b = 0;
    char sinal = ' ';
    printf("Conta (ex.: 6 + 3): ");
    if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
        printf("Não entendi a conta.\n");
        return 1;
    }

    int (*op)(int, int) = NULL;
    if (sinal == '+') {
        op = somar;
    } else if (sinal == '-') {
        op = subtrair;
    } else if (sinal == '*') {
        op = multiplicar;
    }

    if (op == NULL) {
        printf("Não conheço a operação '%c'.\n", sinal);
        return 1;
    }
    printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
    return 0;
}
#include <stdio.h>

int somar(int a, int b)       { return a + b; }
int subtrair(int a, int b)    { return a - b; }
int multiplicar(int a, int b) { return a * b; }
int dividir(int a, int b)     { return a / b; }

int main(void) {
    int a = 0, b = 0;
    char sinal = ' ';
    printf("Conta (ex.: 6 + 3): ");
    if (scanf("%d %c %d", &a, &sinal, &b) != 3) {
        printf("Não entendi a conta.\n");
        return 1;
    }

    int (*op)(int, int) = NULL;
    if (sinal == '+') {
        op = somar;
    } else if (sinal == '-') {
        op = subtrair;
    } else if (sinal == '*') {
        op = multiplicar;
    } else if (sinal == '/') {
        op = dividir;
    }

    if (op == NULL) {
        printf("Não conheço a operação '%c'.\n", sinal);
        return 1;
    }
    printf("%d %c %d = %d\n", a, sinal, b, op(a, b));
    return 0;
}

Depois de cumprir a missão, troque o que está no campo Entrada por 8 / 0, rode de novo e veja o programa morrer com Exceção de ponto flutuante: divisão inteira por zero. Uma calculadora de verdade conferiria o b antes (aula Operadores Aritméticos).


3. Passar uma função para outra: o callback

Um ponteiro para função pode ser parâmetro. Assim, quem chama decide uma parte do trabalho da função chamada. A função aplicar abaixo percorre um array e aplica em cada item "alguma transformação", que ela recebe pronta:

#include <stdio.h>

int dobro(int x)    { return x * 2; }
int quadrado(int x) { return x * x; }

void aplicar(int *v, int n, int (*f)(int)) {
    for (int i = 0; i < n; i++) {
        v[i] = f(v[i]);
    }
}

void mostrar(const int *v, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", v[i]);
    }
    printf("\n");
}

int main(void) {
    int pontos[4] = {1, 2, 3, 4};
    aplicar(pontos, 4, dobro);
    mostrar(pontos, 4);
    aplicar(pontos, 4, quadrado);
    mostrar(pontos, 4);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra aplicar.c -o aplicar
$ ./aplicar
2 4 6 8                  « cada ponto dobrou
4 16 36 64               « e depois foi ao quadrado
int (*f)(int)
O terceiro parâmetro de aplicar é um ponteiro para uma função que recebe um int e devolve um int.
v[i] = f(v[i]);
aplicar não sabe se está dobrando, elevando ao quadrado ou outra coisa. Ela só chama f.
aplicar(pontos, 4, dobro)
Passa a função dobro (sem parênteses!) como argumento.

Uma função passada para outra, que a chama "de volta" na hora certa, se chama callback ("chamar de volta"). É como deixar seu número com a pizzaria: você não fica ligando para perguntar se a pizza saiu; eles chamam de volta quando estiver pronta.

O callback mais famoso: qsort

A biblioteca padrão tem uma função de ordenação, qsort ("quick sort", ordenação rápida), que ordena arrays de qualquer tipo. Como ela não sabe comparar seus dados (números? nomes? fichas de alunos?), você entrega uma função que compara dois itens:

#include <stdio.h>
#include <stdlib.h>

int comparar(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
}

int main(void) {
    int notas[] = {7, 3, 10, 5, 9};
    size_t n = sizeof notas / sizeof notas[0];

    qsort(notas, n, sizeof notas[0], comparar);

    printf("Notas:");
    for (size_t i = 0; i < n; i++) {
        printf(" %d", notas[i]);
    }
    printf("\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ordena.c -o ordena
$ ./ordena
Notas: 3 5 7 9 10
qsort(notas, n, sizeof notas[0], comparar)
O array, quantos itens, o tamanho de cada item e a função que compara. O protótipo é void qsort(void *base, size_t n, size_t tamanho, int (*comparar)(const void *, const void *)): o último parâmetro é um ponteiro para função.
const void *a, const void *b
O qsort entrega os endereços de dois itens como void *, porque não sabe o tipo deles (aula Ponteiro void*). A sua função converte para o tipo certo.
return -1 / 1 / 0
A regra do qsort: negativo se a vem antes, positivo se a vem depois, zero se tanto faz.

Repare que você nunca chama comparar. Quem chama é o qsort, quantas vezes precisar, durante a ordenação. Os detalhes (ordenar nomes, structs, critérios de desempate) estão em Ordenação (qsort), e o bsearch, que usa a mesma ideia para procurar, em Busca.

Por que não return x - y;?

Muita gente escreve a comparação como return x - y;. Funciona com notas pequenas, mas com números grandes a subtração pode estourar o int e dar o sinal errado. As duas comparações com < e > nunca estouram.


4. Um array de ponteiros de função: o menu

Ponteiros para funções podem ir para um array, como qualquer outro valor. Com isso, um menu vira uma tabela: o número escolhido é o índice, e o item do array é a ação.

#include <stdio.h>

void novo_jogo(void)  { printf("Começando um jogo novo...\n"); }
void carregar(void)   { printf("Carregando o jogo salvo...\n"); }
void recordes(void)   { printf("Recorde: Ana, 1200 pontos\n"); }

int main(void) {
    void (*acoes[])(void) = {novo_jogo, carregar, recordes};
    const char *nomes[] = {"Novo jogo", "Carregar", "Recordes"};
    int total = sizeof acoes / sizeof acoes[0];

    for (int i = 0; i < total; i++) {
        printf("%d) %s\n", i + 1, nomes[i]);
    }
    printf("Escolha: ");

    int escolha = 0;
    if (scanf("%d", &escolha) != 1 || escolha < 1 || escolha > total) {
        printf("Opção inválida.\n");
        return 1;
    }
    acoes[escolha - 1]();
    return 0;
}
void (*acoes[])(void)
Leia a partir do nome: acoes é um array ([]) de ponteiros (*) para funções sem parâmetros ((void)) que não devolvem nada (void). O tamanho sai da lista entre chaves: 3.
{novo_jogo, carregar, recordes}
Os endereços das três funções, na ordem do menu.
sizeof acoes / sizeof acoes[0]
Quantos itens o array tem (veja Arrays). Se você acrescentar uma função na lista, o menu cresce sozinho.
escolha < 1 || escolha > total
Conferência obrigatória: um índice fora do array pegaria um "endereço" qualquer da memória e o programa pularia para lá.
acoes[escolha - 1]()
Pega o ponteiro na posição escolhida (o menu começa em 1, o array em 0) e chama a função. Os () no fim são a chamada.
$ gcc -std=c23 -Wall -Wextra menu.c -o menu
$ ./menu
1) Novo jogo
2) Carregar
3) Recordes
Escolha: 3
Recorde: Ana, 1200 pontos
$ ./menu
1) Novo jogo
2) Carregar
3) Recordes
Escolha: 7
Opção inválida.
ÍndiceNome no menuO que acoes[i] guardaacoes[i]() faz
01) Novo jogoendereço de novo_jogoComeçando um jogo novo...
12) Carregarendereço de carregarCarregando o jogo salvo...
23) Recordesendereço de recordesRecorde: Ana, 1200 pontos
O menu como tabela. Sem ponteiros de função, seria um switch com um case por opção; com o array, acrescentar uma opção é acrescentar uma linha na lista.

Experimente: rode e digite um número quando o programa pedir. Depois crie uma função void sair(void) que mostre Até logo! e acrescente-a ao fim dos dois arrays (acoes e nomes): o menu ganha a opção 4 sem você mexer em mais nada.

#include <stdio.h>

void novo_jogo(void)  { printf("Começando um jogo novo...\n"); }
void carregar(void)   { printf("Carregando o jogo salvo...\n"); }
void recordes(void)   { printf("Recorde: Ana, 1200 pontos\n"); }

int main(void) {
    void (*acoes[])(void) = {novo_jogo, carregar, recordes};
    const char *nomes[] = {"Novo jogo", "Carregar", "Recordes"};
    int total = sizeof acoes / sizeof acoes[0];

    for (int i = 0; i < total; i++) {
        printf("%d) %s\n", i + 1, nomes[i]);
    }
    printf("Escolha: ");

    int escolha = 0;
    if (scanf("%d", &escolha) != 1 || escolha < 1 || escolha > total) {
        printf("Opção inválida.\n");
        return 1;
    }
    acoes[escolha - 1]();
    return 0;
}

5. typedef: dando um nome ao tipo

Declarações como void (*acoes[])(void) são difíceis de ler, e ficam piores quando uma função devolve um ponteiro para função. Veja uma função escolher que recebe o sinal e devolve a operação, escrita sem ajuda:

int (*escolher(char sinal))(int, int) {
    if (sinal == '+') return somar;
    if (sinal == '-') return subtrair;
    return NULL;
}

Compila, funciona, e quase ninguém consegue ler: "escolher é uma função que recebe um char e devolve um ponteiro para função que recebe dois int e devolve int". O typedef (aula typedef) dá um nome a esse tipo, e daí em diante você usa o nome:

#include <stdio.h>

typedef int (*Operacao)(int, int);

int somar(int a, int b)       { return a + b; }
int subtrair(int a, int b)    { return a - b; }
int multiplicar(int a, int b) { return a * b; }

Operacao escolher(char sinal) {
    switch (sinal) {
        case '+': return somar;
        case '-': return subtrair;
        case '*': return multiplicar;
        default:  return NULL;
    }
}

int main(void) {
    const char *contas = "+-*/";
    for (const char *s = contas; *s != '\0'; s++) {
        Operacao op = escolher(*s);
        if (op == NULL) {
            printf("8 %c 2: operação desconhecida\n", *s);
        } else {
            printf("8 %c 2 = %d\n", *s, op(8, 2));
        }
    }
    return 0;
}
typedef int (*Operacao)(int, int);
Escreva a declaração do ponteiro como se fosse uma variável e ponha typedef na frente: o "nome da variável" (Operacao) vira o nome do tipo.
Operacao escolher(char sinal)
Agora sim, se lê de primeira: "escolher devolve uma Operacao".
Operacao op = escolher(*s);
Uma variável do tipo Operacao, como int x.
"+-*/"
Uma string usada como lista de sinais; o laço anda por ela com um ponteiro até o '\0' (aula Ponteiros e Strings).
$ gcc -std=c23 -Wall -Wextra typedef.c -o typedef
$ ./typedef
8 + 2 = 10
8 - 2 = 6
8 * 2 = 16
8 / 2: operação desconhecida

Com o typedef, o menu da seção 4 também fica mais claro: typedef void (*Acao)(void); e depois Acao acoes[] = {novo_jogo, carregar, recordes};.

Outro estilo: typedef do tipo da função

Também dá para dar nome ao tipo da função (sem o asterisco) e escrever o * na hora de usar: typedef int Operacao(int, int); e depois Operacao *op = somar;. Alguns projetos preferem assim porque o * fica à vista e ninguém esquece que op é um ponteiro. As duas formas estão certas; escolha uma e use sempre a mesma.


Formas diferentes e qual usar

SituaçãoPrefira
A escolha é simples e a função é usada uma vez, ali mesmoUm if/switch que chama a função direto. Ponteiro de função seria complicação sem ganho.
A função é escolhida num lugar e usada em outro (ou várias vezes)Uma variável ponteiro para função, iniciada com NULL
Uma função precisa de um "pedaço de comportamento" que quem chama decide (ordenar, filtrar, transformar)Um parâmetro ponteiro para função (callback), como o qsort
Muitas opções numeradas (menu, comandos, estados de um jogo)Um array de ponteiros de função, com conferência do índice
O tipo aparece mais de uma vez ou tem mais de um par de parêntesestypedef

Quando dá errado

error: initialization of ‘int (*)(int, int)’ from incompatible pointer type ‘int (*)(int)’
int somar(int a, int b) { return a + b; }
int dobro(int x)        { return x * 2; }
…
    int (*op)(int, int) = dobro;
$ gcc -std=c23 -Wall -Wextra pfe1.c -o pfe1
pfe1.c: In function ‘main’:
pfe1.c:7:27: error: initialization of ‘int (*)(int,  int)’ from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
    7 |     int (*op)(int, int) = dobro;
      |                           ^~~~~
pfe1.c:4:5: note: ‘dobro’ declared here
    4 | int dobro(int x)        { return x * 2; }
      |     ^~~~~

O que significa: o ponteiro espera uma função com dois int (int (*)(int, int) é o jeito de escrever esse tipo sem nome), e dobro recebe um só. Desde o gcc 14 isso é erro (antes era aviso, e o programa chamava a função com os argumentos errados). O Clang do editor diz incompatible function pointer types initializing 'int (*)(int, int)' with an expression of type 'int (int)'.

Como consertar: a função guardada tem de ter exatamente os mesmos parâmetros e o mesmo retorno do ponteiro. Ou ajuste a função, ou declare o ponteiro com o tipo dela (int (*f)(int)).

error: function ‘op’ is initialized like a variable
$ gcc -std=c23 -Wall -Wextra pfe2.c -o pfe2
pfe2.c: In function ‘main’:
pfe2.c:6:5: error: function ‘op’ is initialized like a variable
    6 |     int *op(int, int) = somar;
      |     ^~~

O que significa: faltaram os parênteses em volta de *op. Sem eles, int *op(int, int) é o protótipo de uma função op que devolve int *, e função não pode receber valor com =: "a função op está sendo inicializada como uma variável". O Clang diz illegal initializer (only variables can be initialized).

Como consertar: int (*op)(int, int) = somar;. Os parênteses "grudam" o * no nome.

error: assignment to ‘int (*)(int, int)’ from ‘int’ makes pointer from integer without a cast
$ gcc -std=c23 -Wall -Wextra pfe3.c -o pfe3
pfe3.c: In function ‘main’:
pfe3.c:7:8: error: assignment to ‘int (*)(int,  int)’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
    7 |     op = somar(6, 3);
      |        ^

O que significa: somar(6, 3) chama a função e dá 9, um int. Você tentou guardar o número 9 num ponteiro para função.

Como consertar: para guardar a função, escreva o nome sem parênteses: op = somar;. Os parênteses e os argumentos ficam para a hora de chamar: op(6, 3).

Falha de segmentação (imagem do núcleo gravada) ao chamar o ponteiro
    int (*op)(int, int) = NULL;
    printf("Vou chamar op...\n");
    printf("%d\n", op(6, 3));
$ gcc -std=c23 -Wall -Wextra pfe4.c -o pfe4
$ ./pfe4
Vou chamar op...
Falha de segmentação     (imagem do núcleo gravada) ./pfe4

O que significa: o programa tentou pular para o endereço zero para rodar "a função" que está lá, e não há função nenhuma. Compilou sem aviso, porque o compilador não sabe o que op vai valer. No editor do guia acontece o mesmo: Falha de segmentação (imagem do núcleo gravada), código 139.

Como consertar: antes de chamar um ponteiro que pode estar vazio, confira: if (op != NULL) …. Num array de ações, confira o índice antes de usá-lo.


Referência rápida

EscritaO que é
int (*op)(int, int);Ponteiro para função que recebe dois int e devolve int
int *op(int, int);Cuidado: é uma função que devolve int *
op = somar; / op = &somar;Guarda a função (as duas formas são iguais)
op(6, 3) / (*op)(6, 3)Chama a função guardada (iguais)
int (*op)(int, int) = NULL;Começa sem função; confira com if (op != NULL) antes de chamar
void f(int (*g)(int))Função que recebe outra função (callback)
void (*acoes[])(void) = {a, b, c};Array de ponteiros de função
acoes[i]()Chama a função da posição i
typedef int (*Operacao)(int, int);Dá o nome Operacao ao tipo do ponteiro
qsort(v, n, sizeof v[0], comparar);Ordena com a sua função de comparação (stdlib.h)
int comparar(const void *a, const void *b)A forma que o qsort exige: negativo, zero ou positivo

Exercícios

1. Declarar

Complete para declarar f, um ponteiro para função que recebe um double e devolve double:

double (double);

2. Guardar a função

Complete para que op passe a apontar para a função subtrair:

op = ;

3. typedef

Complete para dar o nome Acao ao tipo "ponteiro para função sem parâmetros que não devolve nada":

typedef void (void);

4. O que declara int *f(int);?

5. No qsort(notas, n, sizeof notas[0], comparar), quem chama a função comparar?

6. Num menu com acoes[escolha - 1](), por que conferir se escolha está entre 1 e o total?

Missão final: ranking do maior para o menor

Missão: o programa ordena as notas da turma da Ana do menor para o maior. Para o ranking, ela quer do maior para o menor. Mude só a função comparar (o qsort fica como está). Dica: basta inverter o que ela devolve. A saída deve ser:

Notas: 10 9 7 5 3
#include <stdio.h>
#include <stdlib.h>

int comparar(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
}

int main(void) {
    int notas[] = {7, 3, 10, 5, 9};
    size_t n = sizeof notas / sizeof notas[0];

    qsort(notas, n, sizeof notas[0], comparar);

    printf("Notas:");
    for (size_t i = 0; i < n; i++) {
        printf(" %d", notas[i]);
    }
    printf("\n");
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int comparar(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    if (x > y) return -1;
    if (x < y) return 1;
    return 0;
}

int main(void) {
    int notas[] = {7, 3, 10, 5, 9};
    size_t n = sizeof notas / sizeof notas[0];

    qsort(notas, n, sizeof notas[0], comparar);

    printf("Notas:");
    for (size_t i = 0; i < n; i++) {
        printf(" %d", notas[i]);
    }
    printf("\n");
    return 0;
}

Quer ir além? Mantenha as duas funções, crescente e decrescente, e escolha qual passar ao qsort com um ponteiro int (*criterio)(const void *, const void *). É o mesmo truque da calculadora, agora decidindo a ordem.


Resumo

  • Funções moram na memória; o nome de uma função, sem parênteses, é o endereço dela.
  • int (*op)(int, int); declara um ponteiro para função; os parênteses em (*op) são obrigatórios (int *op(int, int) seria uma função que devolve int *).
  • Guarde com op = somar; e chame com op(6, 3). O tipo da função guardada tem de bater exatamente com o do ponteiro (desde o gcc 14, é erro se não bater).
  • Comece com NULL e confira antes de chamar: chamar um ponteiro nulo dá falha de segmentação.
  • Passar uma função como parâmetro é um callback; o qsort recebe a sua função de comparação e a chama quando precisa.
  • Um array de ponteiros de função transforma um menu numa tabela (confira o índice!).
  • typedef int (*Operacao)(int, int); dá nome ao tipo e deixa as declarações legíveis.