Como funcionam os ponteiros de função em C?


1233

Ultimamente, tive alguma experiência com ponteiros de função em C.

Continuando com a tradição de responder às suas próprias perguntas, decidi fazer um pequeno resumo do básico, para aqueles que precisam mergulhar rapidamente no assunto.


35
Além disso: Para uma análise mais aprofundada dos ponteiros C, consulte blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge . Além disso, a programação desde o início mostra como eles funcionam no nível da máquina. Compreender o "modelo de memória" de C é muito útil para entender como os ponteiros de C funcionam.
Abbafei

8
Ótima informação. Pelo título, porém, eu teria esperado para realmente ver uma explicação de como "ponteiros de função de trabalho", e não como eles são codificados :)
Bogdan Alexandru

Respostas:


1478

Ponteiros de função em C

Vamos começar com uma função básica para a qual apontaremos :

int addInt(int n, int m) {
    return n+m;
}

Primeiro, vamos definir um ponteiro para uma função que recebe 2 se intretorna um int:

int (*functionPtr)(int,int);

Agora podemos apontar com segurança para nossa função:

functionPtr = &addInt;

Agora que temos um ponteiro para a função, vamos usá-lo:

int sum = (*functionPtr)(2, 3); // sum == 5

Passar o ponteiro para outra função é basicamente o mesmo:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

Também podemos usar ponteiros de função em valores de retorno (tente acompanhar, fica confuso):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Mas é muito melhor usar um typedef:

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}

19
Obrigado pela ótima informação. Você poderia adicionar algumas dicas sobre onde os ponteiros de função são usados ​​ou são particularmente úteis?
509 Rich.Carpenter

326
"functionPtr = & addInt;" também pode ser escrito (e geralmente é) como "functionPtr = addInt;" o que também é válido, já que o padrão diz que um nome de função nesse contexto é convertido no endereço da função.
Hlovdal 9/05/09

22
hlovdal, neste contexto, é interessante explicar que é isso que permite escrever functionPtr = ****************** addInt;
Johannes Schaub - litb 10/05

105
@ Rich.Carpenter Eu sei que isso é 4 anos tarde demais, mas acho que outras pessoas podem se beneficiar com isso: Indicadores de função são úteis para passar funções como parâmetros para outras funções . Foram necessárias muitas pesquisas para encontrar essa resposta por algum motivo estranho. Então, basicamente, ele fornece funcionalidade de pseudo-classe C em C
giant91

22
@ Rich.Carpenter: ponteiros de função são bons para detecção de CPU em tempo de execução. Tenha várias versões de algumas funções para tirar proveito do SSE, popcnt, AVX, etc. Na inicialização, defina os ponteiros de função para a melhor versão de cada função para a CPU atual. No seu outro código, basta chamar através do ponteiro de função em vez de ter ramificações condicionais nos recursos da CPU em qualquer lugar. Em seguida, você pode fazer uma lógica complicada sobre como decidir isso bem, mesmo que essa CPU suporte pshufb, ela é lenta, portanto a implementação anterior ainda é mais rápida. x264 / x265 usam isso extensivamente e são de código aberto.
Peter Cordes

304

Ponteiros de função em C podem ser usados ​​para executar programação orientada a objetos em C.

Por exemplo, as seguintes linhas são escritas em C:

String s1 = newString();
s1->set(s1, "hello");

Sim, a ->falta de um newoperador é um sacrifício total, mas com certeza parece implicar que estamos definindo o texto de alguma Stringclasse "hello".

Ao usar os ponteiros de função, é possível emular os métodos em C .

Como isso é realizado?

A Stringclasse é na verdade uma structcom vários ponteiros de função que funcionam como uma maneira de simular métodos. A seguir, uma declaração parcial da Stringclasse:

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Como pode ser visto, os métodos da Stringclasse são, na verdade, indicadores de função para a função declarada. Ao preparar a instância de String, a newStringfunção é chamada para configurar os ponteiros de função para suas respectivas funções:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Por exemplo, a getStringfunção que é chamada invocando o getmétodo é definida da seguinte maneira:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Uma coisa que pode ser notada é que não existe o conceito de uma instância de um objeto e que existem métodos que realmente fazem parte de um objeto; portanto, um "auto-objeto" deve ser passado em cada chamada. (E internalé apenas um oculto structque foi omitido da listagem de códigos anteriormente - é uma maneira de ocultar informações, mas isso não é relevante para os ponteiros de função.)

Portanto, ao invés de ser capaz de fazer s1->set("hello");, é preciso passar o objeto para executar a ação s1->set(s1, "hello").

Com essa explicação menor ter que passar uma referência a si mesmo fora do caminho, vamos passar para a próxima parte, que é herança em C .

Digamos que queremos criar uma subclasse de String, digamos, um ImmutableString. A fim de tornar o imutável string, o setmétodo não será acessível, mantendo o acesso a gete length, e forçar o "construtor" para aceitar um char*:

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

Basicamente, para todas as subclasses, os métodos disponíveis são mais uma vez indicadores de função. Desta vez, a declaração para o setmétodo não está presente, portanto, não pode ser chamada em a ImmutableString.

Quanto à implementação do ImmutableString, o único código relevante é a função "construtor", a newImmutableString:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

Ao instanciar o ImmutableString, os ponteiros de função para os métodos gete lengthrealmente se referem ao método String.gete String.length, passando pela basevariável que é um Stringobjeto armazenado internamente .

O uso de um ponteiro de função pode obter a herança de um método de uma superclasse.

Podemos ainda continuar a polimorfismo em C .

Se, por exemplo, queremos mudar o comportamento do lengthmétodo para retornar 0o tempo todo na ImmutableStringclasse por algum motivo, tudo o que precisa ser feito é:

  1. Adicione uma função que servirá como lengthmétodo de substituição .
  2. Vá para o "construtor" e defina o ponteiro da função para o lengthmétodo de substituição .

A adição de um lengthmétodo de substituição em ImmutableStringpode ser executada adicionando um lengthOverrideMethod:

int lengthOverrideMethod(const void* self)
{
    return 0;
}

Em seguida, o ponteiro de função para o lengthmétodo no construtor é conectado ao lengthOverrideMethod:

ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

Agora, em vez de ter um comportamento idêntico para o lengthmétodo na ImmutableStringclasse que a Stringclasse, agora o lengthmétodo fará referência ao comportamento definido na lengthOverrideMethodfunção.

Devo acrescentar um aviso de que ainda estou aprendendo a escrever com um estilo de programação orientado a objetos em C; portanto, provavelmente há pontos que não expliquei bem ou que podem estar errados em termos da melhor maneira de implementar o OOP em C. Mas meu objetivo era tentar ilustrar um dos muitos usos de ponteiros de função.

Para obter mais informações sobre como executar a programação orientada a objetos em C, consulte as seguintes perguntas:


22
Esta resposta é horrível! Não apenas implica que o OO depende de alguma forma da notação de pontos, mas também incentiva a colocar lixo em seus objetos!
Alexei Averchenko

27
Isso é OO certo, mas não chega nem perto do OO estilo C. O que você implementou de maneira incorreta é OO baseado em protótipo no estilo Javascript. Para obter OO no estilo C ++ / Pascal, você precisa: 1. Ter uma estrutura const para uma tabela virtual de cada classe com membros virtuais. 2. Tenha um ponteiro para essa estrutura em objetos polimórficos. 3. Chame métodos virtuais por meio da tabela virtual e todos os outros métodos diretamente - geralmente seguindo algumas ClassName_methodNameconvenções de nomenclatura de funções. Somente então você obtém os mesmos custos de tempo de execução e armazenamento que em C ++ e Pascal.
Reintegrar Monica

19
Trabalhar OO com um idioma que não pretende ser OO é sempre uma má ideia. Se você deseja OO e ainda possui C, basta trabalhar com C ++.
Rbaleksandar

20
@rbaleksandar Diga isso aos desenvolvedores do kernel do Linux. "sempre uma má idéia" é estritamente sua opinião, com a qual discordo firmemente.
Jonathon Reinhart

6
Eu gosto desta resposta, mas não lançam malloc
gato

227

O guia para ser demitido: Como abusar de ponteiros de função no GCC em máquinas x86, compilando seu código manualmente:

Esses literais de cadeia de caracteres são bytes de código de máquina x86 de 32 bits. 0xC3é uma retinstrução x86 .

Você normalmente não escreveria isso à mão, escreveria em linguagem assembly e, em seguida, usaria um assembler nasmpara montá-lo em um binário plano que você despeja em um literal de string C.

  1. Retorna o valor atual no registro EAX

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
  2. Escreva uma função de swap

    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. Escreva um contador de loop for para 1000, chamando alguma função cada vez

    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
  4. Você pode até escrever uma função recursiva que conta até 100

    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

Observe que os compiladores colocam literais de seqüência de caracteres na .rodataseção (ou .rdatano Windows), que é vinculada como parte do segmento de texto (junto com o código para funções).

O segmento de texto tem permissão de leitura + Exec, então lançando strings literais de ponteiros de função funciona sem a necessidade mprotect()ou VirtualProtect()chamadas de sistema como você precisaria para memória alocada dinamicamente. (Ou gcc -z execstackvincula o programa à pilha + segmento de dados + heap executável, como um hack rápido).


Para desmontá-los, você pode compilar isso para colocar um rótulo nos bytes e usar um desmontador.

// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

Compilando gcc -c -m32 foo.ce desmontando objdump -D -rwC -Mintel, podemos obter a montagem e descobrir que esse código viola a ABI derrotando o EBX (um registro preservado de chamada) e geralmente é ineficiente.

00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

Esse código de máquina (provavelmente) funcionará no código de 32 bits no Windows, Linux, OS X e assim por diante: as convenções de chamada padrão em todos esses sistemas operacionais passam argumentos na pilha, em vez de serem mais eficientes nos registros. Mas o EBX é preservado em todas as convenções de chamada normais, portanto, usá-lo como um registro de rascunho sem salvá-lo / restaurá-lo pode facilmente causar uma falha no chamador.


8
Nota: isso não funciona se a Prevenção de execução de dados estiver ativada (por exemplo, no Windows XP SP2 +), porque as seqüências de caracteres C não são normalmente marcadas como executáveis.
SecurityMatt

5
Oi Matt! Dependendo do nível de otimização, o GCC geralmente alinha as constantes de seqüência de caracteres no segmento TEXT; portanto, isso funcionará mesmo na versão mais recente do Windows, desde que você não desaprove esse tipo de otimização. (IIRC, a versão mingw no momento do meu post mais de dois anos inlines strings literais no nível de otimização padrão)
Lee

10
alguém poderia explicar o que está acontecendo aqui? O que são esses literais de aparência estranha?
ajay

56
@ajay Parece que ele está escrevendo valores hexidecimais brutos (por exemplo, '\ x00' é o mesmo que '/ 0', ambos são iguais a 0) em uma sequência de caracteres e, em seguida, transforma a sequência em um ponteiro de função C e, em seguida, executa o ponteiro da função C porque ele é o diabo.
ejk314

3
oi FUZxxl, acho que pode variar com base no compilador e na versão do sistema operacional. O código acima parece funcionar bem no codepad.org; codepad.org/FMSDQ3ME
Lee

115

Um dos meus usos favoritos para ponteiros de função é como iteradores baratos e fáceis -

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}

7
Você também deve passar um ponteiro para os dados especificados pelo usuário, se desejar extrair alguma saída das iterações (pense em fechamentos).
Alexei Averchenko

1
Acordado. Todos os meus iterators parecido com este: int (*cb)(void *arg, ...). O valor de retorno do iterador também me permite parar mais cedo (se diferente de zero).
Jonathon Reinhart

24

Os ponteiros de função tornam-se fáceis de declarar depois que você tiver os declaradores básicos:

  • id: ID: ID é um
  • Ponteiro: *D: ponteiro D para
  • Função: D(<parameters>): D função de tomada de <parâmetros >retornar

Enquanto D é outro declarador criado usando essas mesmas regras. No final, em algum lugar, termina com ID(veja um exemplo abaixo), que é o nome da entidade declarada. Vamos tentar criar uma função que leva um ponteiro para uma função que não aceita nada e retorna int, e retorna um ponteiro para uma função que aceita um char e retorna int. Com defs tipo é assim

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Como você vê, é muito fácil construí-lo usando typedefs. Sem typedefs, também não é difícil com as regras do declarador acima, aplicadas de forma consistente. Como você vê, perdi a parte que o ponteiro aponta e a coisa que a função retorna. Isso é o que aparece no lado esquerdo da declaração e não é interessante: será adicionado no final se alguém já tiver construído o declarador. Vamos fazer isso. Construindo-o de forma consistente, primeiro prolixo - mostrando a estrutura usando [e ]:

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Como você vê, pode-se descrever um tipo completamente anexando declaradores um após o outro. A construção pode ser feita de duas maneiras. Uma é de baixo para cima, começando com a coisa mais certa (folhas) e trabalhando até o identificador. A outra maneira é de cima para baixo, começando no identificador, indo até as folhas. Eu vou mostrar para os dois lados.

Debaixo para cima

A construção começa com a coisa à direita: a coisa retornada, que é a função que leva char. Para manter os declaradores distintos, vou numerá-los:

D1(char);

Inserido o parâmetro char diretamente, pois é trivial. Adicionando um ponteiro ao declarador substituindo D1por *D2. Note que temos que colocar parênteses *D2. Isso pode ser conhecido consultando a precedência do *-operatoroperador de chamada de função e (). Sem nossos parênteses, o compilador o leria *(D2(char p)). Mas isso não seria mais um substituto simples do D1 *D2, é claro. Parênteses sempre são permitidos em torno dos declaradores. Portanto, você não cometerá nada de errado se adicionar muitos deles, na verdade.

(*D2)(char);

O tipo de retorno está completo! Agora, vamos substituir D2pelo declarator função de tomada função <parameters>retornar , o que é D3(<parameters>)que estamos agora.

(*D3(<parameters>))(char)

Observe que nenhum parênteses é necessário, pois queremos D3 ser um declarador de função e não um declarador de ponteiro neste momento. Ótimo, a única coisa que resta são os parâmetros para isso. O parâmetro é feito exatamente da mesma maneira que fizemos o tipo de retorno, apenas com charsubstituído por void. Então eu vou copiá-lo:

(*D3(   (*ID1)(void)))(char)

Eu substituí D2por ID1, já que terminamos com esse parâmetro (já é um ponteiro para uma função - não é necessário outro declarador). ID1será o nome do parâmetro. Agora, como eu disse acima, no final, adicionamos o tipo que todos os declaradores modificam - o que aparece à esquerda de todas as declarações. Para funções, esse se torna o tipo de retorno. Para ponteiros, o tipo apontado etc ... É interessante quando escrito o tipo, ele aparecerá na ordem oposta, à direita :) De qualquer forma, substituindo-o, produz a declaração completa. Ambos os tempos, é intclaro.

int (*ID0(int (*ID1)(void)))(char)

Chamei o identificador da função ID0nesse exemplo.

Careca

Isso começa no identificador à esquerda na descrição do tipo, envolvendo esse declarador enquanto percorremos o caminho à direita. Comece com a função tendo <parâmetros >retornando

ID0(<parameters>)

A próxima coisa na descrição (depois de "retornar") foi apontar para . Vamos incorporar:

*ID0(<parameters>)

Em seguida, o próximo passo<> foi executar os parâmetros retornando . O parâmetro é um caractere simples, então o colocamos imediatamente novamente, pois é realmente trivial.

(*ID0(<parameters>))(char)

Observe os parênteses que adicionamos, já que queremos novamente que os *vínculos primeiro e depois o (char). Caso contrário, ele iria ler a função de tomar <parâmetros de >função retornando ... . Não, funções que retornam funções nem são permitidas.

Agora só precisamos colocar <parâmetros >. Vou mostrar uma versão curta da deriveração, pois acho que você já tem a ideia de como fazê-lo.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Basta colocar intdiante dos declarantes, como fizemos de baixo para cima, e terminamos

int (*ID0(int (*ID1)(void)))(char)

A coisa legal

É de baixo para cima ou de cima para baixo? Estou acostumado a subir de cima para baixo, mas algumas pessoas podem ficar mais confortáveis ​​com a descida. É uma questão de gosto, eu acho. Aliás, se você aplicar todos os operadores nessa declaração, você receberá um int:

int v = (*ID0(some_function_pointer))(some_char);

Essa é uma boa propriedade de declarações em C: a declaração afirma que, se esses operadores forem usados ​​em uma expressão usando o identificador, ele produzirá o tipo à esquerda. É assim também para matrizes.

Espero que você tenha gostado deste pequeno tutorial! Agora podemos vincular isso quando as pessoas se perguntam sobre a estranha sintaxe de declaração de funções. Eu tentei colocar o mínimo de C internos possível. Sinta-se livre para editar / consertar as coisas nele.


24

Outro bom uso para ponteiros de função:
alternando entre versões sem problemas

Eles são muito úteis para usar quando você deseja funções diferentes em momentos diferentes ou fases diferentes de desenvolvimento. Por exemplo, estou desenvolvendo um aplicativo em um computador host que possui um console, mas a versão final do software será colocada em um Avnet ZedBoard (que possui portas para monitores e consoles, mas eles não são necessários / desejados para o último lançamento). Portanto, durante o desenvolvimento, usarei printfpara exibir mensagens de status e de erro, mas quando terminar, não quero nada impresso. Aqui está o que eu fiz:

version.h

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

Em version.cvou definir os 2 protótipos de função presentes emversion.h

version.c

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Observe como o ponteiro da função é prototipado version.hcomo

void (* zprintf)(const char *, ...);

Quando é referenciado no aplicativo, ele começa a ser executado onde quer que esteja apontando, o que ainda não foi definido.

Em version.c, observe na board_init()função em que zprintfé atribuída uma função exclusiva (cuja assinatura da função corresponde), dependendo da versão definida emversion.h

zprintf = &printf; O zprintf chama printf para fins de depuração

ou

zprintf = &noprint; O zprintf simplesmente retorna e não executa código desnecessário

A execução do código ficará assim:

mainProg.c

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

O código acima será usado printfse estiver no modo de depuração ou não fará nada se estiver no modo de liberação. Isso é muito mais fácil do que passar pelo projeto inteiro e comentar ou excluir o código. Tudo o que eu preciso fazer é alterar a versão version.he o código fará o resto!


4
Você perde muito tempo de desempenho. Em vez disso, você pode usar uma macro que habilita e desabilita uma seção de código baseada em Debug / Release.
AlphaGoku

19

O ponteiro de função é geralmente definido por typedefe usado como parâmetro e valor de retorno.

As respostas acima já explicaram bastante, apenas dou um exemplo completo:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

14

Um dos grandes usos para ponteiros de função em C é chamar uma função selecionada em tempo de execução. Por exemplo, a biblioteca de tempo de execução C possui duas rotinas qsorte bsearch, que levam um ponteiro para uma função chamada para comparar dois itens que estão sendo classificados; isso permite que você classifique ou pesquise, respectivamente, qualquer coisa, com base nos critérios que deseja usar.

Um exemplo muito básico, se houver uma função chamada print(int x, int y)que, por sua vez, pode exigir a chamada de uma função ( add()ou sub()que são do mesmo tipo), o que faremos, adicionaremos um argumento de ponteiro de print()função à função, como mostrado abaixo :

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

A saída é:

o valor é: 410 o
valor é: 390


10

A função Partir do zero possui um endereço de memória de onde eles começam a executar. Na linguagem Assembly, eles são chamados como (chamada "endereço de memória da função"). Agora volte para C Se a função tiver um endereço de memória, eles poderão ser manipulados por ponteiros em C. Então, pelas regras de C

1.Primeiro você precisa declarar um ponteiro para funcionar 2.Passar o endereço da função desejada

**** Nota-> as funções devem ser do mesmo tipo ****

Este programa simples ilustrará todas as coisas.

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

insira a descrição da imagem aquiDepois disso, veja Como a máquina os entende.Glimpse das instruções da máquina do programa acima na arquitetura de 32 bits.

A área de marca vermelha está mostrando como o endereço está sendo trocado e armazenado no eax. Então é uma instrução de chamada no eax. eax contém o endereço desejado da função.


8

Um ponteiro de função é uma variável que contém o endereço de uma função. Como é uma variável de ponteiro, embora com algumas propriedades restritas, você pode usá-lo como faria com qualquer outra variável de ponteiro nas estruturas de dados.

A única exceção em que consigo pensar é tratar o ponteiro da função como apontando para algo diferente de um único valor. Fazer a aritmética do ponteiro aumentando ou diminuindo um ponteiro de função ou adicionando / subtraindo um deslocamento a um ponteiro de função não é realmente útil, pois um ponteiro de função aponta apenas para uma única coisa, o ponto de entrada de uma função.

O tamanho de uma variável de ponteiro de função, o número de bytes ocupados pela variável, pode variar dependendo da arquitetura subjacente, por exemplo, x32 ou x64 ou o que for.

A declaração para uma variável de ponteiro de função precisa especificar o mesmo tipo de informação que uma declaração de função para que o compilador C faça os tipos de verificação que normalmente faz. Se você não especificar uma lista de parâmetros na declaração / definição do ponteiro de função, o compilador C não poderá verificar o uso de parâmetros. Há casos em que essa falta de verificação pode ser útil, mas lembre-se de que uma rede de segurança foi removida.

Alguns exemplos:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

As duas primeiras declarações são um pouco semelhantes:

  • funcé uma função que recebe um inte um char *e retorna umint
  • pFuncé um ponteiro de função ao qual é atribuído o endereço de uma função que recebe um inte um char *e retorna umint

Portanto, do acima exposto, poderíamos ter uma linha de origem na qual o endereço da função func()é atribuído à variável de ponteiro da função pFunccomo em pFunc = func;.

Observe a sintaxe usada com uma declaração / definição de ponteiro de função na qual parênteses são usados ​​para superar as regras de precedência do operador natural.

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Vários exemplos de uso diferentes

Alguns exemplos de uso de um ponteiro de função:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

Você pode usar listas de parâmetros de comprimento variável na definição de um ponteiro de função.

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

Ou você não pode especificar uma lista de parâmetros. Isso pode ser útil, mas elimina a oportunidade do compilador C de executar verificações na lista de argumentos fornecida.

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

Moldes de estilo C

Você pode usar projeções de estilo C com ponteiros de função. No entanto, esteja ciente de que um compilador C pode ser negligente quanto a verificações ou fornecer avisos em vez de erros.

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Comparar ponteiro de função com igualdade

Você pode verificar se um ponteiro de função é igual a um endereço de função específico usando uma ifinstrução, embora não tenha certeza de quão útil isso seria. Outros operadores de comparação parecem ter ainda menos utilidade.

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Uma matriz de ponteiros de função

E se você quiser ter uma matriz de ponteiros de função, cada um dos elementos cuja lista de argumentos possui diferenças, poderá definir um ponteiro de função com a lista de argumentos não especificada (não o voidque significa que não há argumentos, mas apenas não especificado), algo como o seguinte, embora você pode ver avisos do compilador C. Isso também funciona para um parâmetro de ponteiro de função para uma função:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

Estilo C namespaceUsando Global structcom Ponteiros de Função

Você pode usar a staticpalavra-chave para especificar uma função cujo nome é escopo do arquivo e atribuí-la a uma variável global como uma maneira de fornecer algo semelhante à namespacefuncionalidade do C ++.

Em um arquivo de cabeçalho, defina uma estrutura que será nosso espaço para nome, juntamente com uma variável global que a utilize.

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Em seguida, no arquivo de origem C:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Isso seria usado especificando o nome completo da variável de estrutura global e o nome do membro para acessar a função. O constmodificador é usado no global para que não possa ser alterado por acidente.

int abcd = FuncThingsGlobal.func1 (a, b);

Áreas de aplicação de ponteiros de função

Um componente de biblioteca DLL pode fazer algo semelhante à namespaceabordagem de estilo C , na qual uma interface de biblioteca específica é solicitada a partir de um método de fábrica em uma interface de biblioteca que suporta a criação de structponteiros de função contendo essa interface. Essa interface de biblioteca carrega a versão DLL solicitada, cria uma estrutura com os ponteiros de função necessários e, em seguida, retorna a estrutura ao chamador solicitante para uso.

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

e isso pode ser usado como em:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

A mesma abordagem pode ser usada para definir uma camada de hardware abstrata para código que usa um modelo específico do hardware subjacente. Os ponteiros de função são preenchidos com funções específicas de hardware por uma fábrica para fornecer a funcionalidade específica de hardware que implementa as funções especificadas no modelo de hardware abstrato. Isso pode ser usado para fornecer uma camada de hardware abstrata usada pelo software que chama uma função de fábrica para obter a interface específica da função de hardware e, em seguida, usa os ponteiros de função fornecidos para executar ações para o hardware subjacente sem a necessidade de conhecer detalhes de implementação sobre o destino específico .

Ponteiros de função para criar delegados, manipuladores e retornos de chamada

Você pode usar ponteiros de função como uma maneira de delegar alguma tarefa ou funcionalidade. O exemplo clássico em C é o ponteiro da função de delegação de comparação usado com as funções da biblioteca C padrão qsort()e bsearch()para fornecer a ordem de intercalação para classificar uma lista de itens ou executar uma pesquisa binária em uma lista classificada de itens. O delegado da função de comparação especifica o algoritmo de intercalação usado na classificação ou na pesquisa binária.

Outro uso é semelhante à aplicação de um algoritmo a um contêiner da Biblioteca de Modelos Padrão do C ++.

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Outro exemplo é o código fonte da GUI, no qual um manipulador para um evento específico é registrado, fornecendo um ponteiro de função que é realmente chamado quando o evento acontece. A estrutura do Microsoft MFC com seus mapas de mensagens usa algo semelhante para lidar com mensagens do Windows entregues em uma janela ou thread.

Funções assíncronas que requerem um retorno de chamada são semelhantes a um manipulador de eventos. O usuário da função assíncrona chama a função assíncrona para iniciar alguma ação e fornece um ponteiro de função que a função assíncrona chamará quando a ação for concluída. Nesse caso, o evento é a função assíncrona que completa sua tarefa.


0

Como os ponteiros de função geralmente são retornos de chamada digitados, você pode dar uma olhada nos retornos de chamada seguros do tipo . O mesmo se aplica aos pontos de entrada, etc, de funções que não são retornos de chamada.

C é bastante inconstante e perdoa ao mesmo tempo :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.