Pergunta

Eu tinha alguma experiência, ultimamente, com a função de ponteiros no C.

Assim acontecendo com a tradição de responder a suas próprias perguntas, eu decidi fazer um pequeno resumo do básico, para quem precisa de um rápido mergulho para o assunto.

Foi útil?

Solução

Ponteiros de função em c

Vamos começar com uma função básica que seremos apontando para:

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

Primeira coisa, vamos definir um ponteiro para uma função que recebe 2 ints e retorna um int:

int (*functionPtr)(int,int);

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

functionPtr = &addInt;

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

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;
}

Outras dicas

Ponteiros de função em C pode ser usado para executar a programação orientada a objetos em C.

Por exemplo, as seguintes linhas é escrito em C:

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

Sim, o -> e a falta de um new o operador é um morto dar, mas com certeza parece implicar que nós estamos estabelecendo o texto de alguns String classe a ser "hello".

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

Como isso é feito?

O String classe é, na verdade, um struct com um monte de ponteiros de função, que atuam como uma forma de simular métodos.O seguinte é uma declaração parcial de String classe:

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 de String classe são, na verdade, ponteiros de função para a função declarada.Preparar a instância do String, o newString a função é chamada, a fim de configurar a função de ponteiros para as 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, o getString função que é chamada invocando o get o método é definido como o seguinte:

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

Uma coisa que pode ser notado é que não existe o conceito de uma instância de um objeto e ter métodos que são realmente uma parte de um objeto e, portanto, uma "auto-objeto" deve ser passado em cada invocação.(E o internal é apenas um oculto struct o que foi omitido da lista de código anterior -- é uma forma de realização de esconder informações, mas que não é relevante para ponteiros de função.)

Então, em vez de ser capaz de fazer s1->set("hello");, deve passar o objeto para executar a ação s1->set(s1, "hello").

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

Vamos dizer que nós queremos fazer uma subclasse de String, dizer um ImmutableString.A fim de tornar a cadeia imutável, a set método não será acessível, mantendo o acesso ao get e 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 os ponteiros de função.Desta vez, a declaração para a set o método não está presente, portanto, ele não pode ser chamado em um ImmutableString.

Como para a implementação do ImmutableString, o único código relevante é o "construtor" de função, o 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;
}

No instanciar o ImmutableString, os ponteiros de função para o get e length métodos, na verdade, referem-se à String.get e String.length o método, por meio de base variável que é um armazenadas internamente String objecto.

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

Podemos continuar polimorfismo em C.

Se, por exemplo, nós queríamos mudar o comportamento do length método para retornar 0 o tempo todo no ImmutableString de classe, por algum motivo, tudo o que teria de ser feito é:

  1. Adicionar uma função que vai servir como o principal length o método.
  2. Vá para o "construtor" e defina o ponteiro de função para a substituir length o método.

A adição de um interesse length método em ImmutableString pode ser realizada pela adição de um lengthOverrideMethod:

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

Em seguida, o ponteiro de função para a length o método construtor é ligado 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 length método em ImmutableString classe a String classe, agora o length método irá referir-se ao comportamento definido no lengthOverrideMethod função.

Devo acrescentar um aviso de que eu ainda estou aprendendo a escrever com um object-oriented estilo de programação em C, então provavelmente há pontos que eu não explicar bem, ou pode ser apenas fora de marca em termos de como melhor implementar OOP em C.Mas o 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 objeto em C, por favor, consulte as seguintes perguntas:

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 string são bytes do código da máquina x86 de 32 bits. 0xC3 é um x86 ret instrução.

Você normalmente não os escreveria manualmente, escreveria na linguagem de montagem e depois usaria um assembler como nasm Para montá -lo em um binário plano que você hexdump em uma corda C literal.

  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 troca

    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 para 1000, chamando alguma função a 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 para 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 cordas no .rodata Seção (ou .rdata no Windows), que está vinculado como parte do segmento de texto (junto com o código para funções).

O segmento de texto leu a permissão de Exec, então lançar literais de cordas para funcionar ponteiros funciona sem precisar mprotect() ou VirtualProtect() Chamadas de sistema como você precisaria para a memória alocada dinamicamente. (Ou gcc -z execstack Links o programa com o segmento de dados da Stack + Data + Heap executável, como um hack rápido.)


Para desmontar isso, 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 com gcc -c -m32 foo.c e desmontando com objdump -D -rwC -Mintel, podemos obter a Assembléia e descobrir que esse código viola o ABI batendo 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 da máquina (provavelmente) funcionará em código de 32 bits no Windows, Linux, OS X, e assim por diante: as convenções de chamada padrão em todos os OSs passam o ARGS na pilha, em vez de mais eficientemente nos registros. Mas o EBX é preservado em todas as convenções de chamada normal; portanto, usá-lo como um registro de arranhões sem salvar/restaurá-lo pode facilmente fazer com que o chamador falhe.

Um dos meus usos favoritos para ponteiros de função é como iteradores fáceis 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);
}

Ponteiros de função, tornar-se fácil para declarar uma vez que você tem o básico declarators:

  • identificação: ID: ID é um
  • Ponteiro: *D: D ponteiro para
  • Função: D(<parameters>): D função tendo <parâmetros> voltar

Enquanto D é outro declarador criado usando as mesmas regras.No final, em algum lugar, ele termina com ID (veja abaixo um exemplo), que é o nome da declarada entidade.Vamos tentar construir uma função de tomar um ponteiro para uma função que nada e retornar int, e retornando um ponteiro para uma função tendo um char e retornar int.Com tipo-defs é assim

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

Como você vê, é muito fácil para construí-la utilizando typedefs.Sem typedefs, não é difícil com o acima declarador de regras, aplicadas de forma consistente.Como você vê eu perdi a parte que o ponteiro aponta para, e a coisa que a função retorna.Que é o que aparece na extrema esquerda da declaração, e não é de interesse:É adicionado no final se construiu o declarador já.Vamos fazer isso.Construir-se de forma consistente, o 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 acrescentando declarators um após o outro.A construção pode ser feita de duas formas.É de baixo para cima, começando com o direito de coisa (folhas) e trabalhar o caminho através até o identificador.A outra forma é o de cima para baixo, começando pelo identificador, trabalhando até as folhas.Eu vou mostrar duas formas.

De Baixo Para Cima

A construção inicia-se com a coisa, o direito de:A coisa voltou, que é a função de tomar char.Para manter o declarators distintos, vou enumerá-las:

D1(char);

Inserido o caractere parâmetro diretamente, uma vez que é trivial.Adicionar um ponteiro para declarador substituindo D1 por *D2.Note que temos para envolver entre parênteses *D2.Que pode ser conhecido, observando-se a precedência da *-operator e a função-operador de chamada de ().Sem o nosso entre parênteses, o compilador iria lê-lo como *(D2(char p)).Mas essa não seria uma simples substituição de D1 por *D2 mais, é claro.Parênteses são sempre permitiu que cerca de declarators.Para que você não faça nada de errado se você adiciona muito deles, na verdade.

(*D2)(char);

Tipo de retorno é completo!Agora, vamos substituir D2 pela função declarador função tendo <parameters> voltar, o que é D3(<parameters>) o que somos agora.

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

Note que sem os parênteses são necessários, desde que quer D3 para ser uma função declarador e não um ponteiro declarador de este tempo.Ótimo, o única coisa que resta é os parâmetros para ele.O parâmetro é feito exatamente o mesmo que temos feito o tipo de retorno, apenas com char substituído por void.Então eu vou copiar:

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

Eu já substituiu D2 por ID1, como estamos a terminar com esse parâmetro (isso já é um ponteiro para uma função, não há necessidade de outro declarador). ID1 será o nome do parâmetro.Agora, o que eu disse acima, no final acrescenta o tipo que todos aqueles declarador de modificar o que aparece na extrema esquerda de cada declaração.Para funções, que torna-se o tipo de retorno.Para ponteiros do apontado tipo, etc...É interessante quando escrito do tipo, ele vai aparecer na ordem oposta, no direito :) de qualquer forma, a substituir os rendimentos a declaração completa.Em ambas as vezes, int do curso.

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

Chamei o identificador da função ID0 no exemplo.

De Cima Para Baixo

Este começa no identificador na extrema esquerda da descrição do tipo de quebra que declarador de como andam o nosso caminho através do direito.Iniciar com função tendo <parâmetros> voltar

ID0(<parameters>)

A próxima coisa na descrição (depois de "o retorno") foi ponteiro para.Vamos incorporá-lo:

*ID0(<parameters>)

Então a próxima coisa foi functon tendo <parâmetros> voltar.O parâmetro é um simples char, então podemos colocá-lo na direita para fora novamente, já que é muito trivial.

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

Note os parênteses adicionado, desde que mais quero que o * liga-se primeiro, e em seguida, o (char).Caso contrário, ele iria ler função tendo <parâmetros> função retornando ....Nãos, funções que retornam funções não são permitidas.

Agora só precisamos colocar <parâmetros>.Vou mostrar uma versão curta do deriveration, desde que eu acho que você já está por agora ter a idéia de como fazê-lo.

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

Basta colocar int antes de o declarators como fizemos com baixo para cima, e estamos acabados

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

A coisa agradável

É bottom-up ou top-down melhor?Eu estou acostumado a bottom-up, mas algumas pessoas podem ser mais confortáveis com o topo para baixo.É uma questão de gosto, eu acho.Aliás, se você aplicar todos os operadores na declaração, você vai acabar ficando um int:

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

Essa é uma bela propriedade de declarações em C:A declaração afirma que, se esses operadores são usados em uma expressão usando o identificador e, em seguida, ele produz o tipo na extrema esquerda.É como que para matrizes também.

Espero que tenha gostado deste tutorial!Agora nós podemos ligar para isso quando as pessoas perguntam sobre a estranha sintaxe de declaração de funções.Eu tentei colocar como pouco C internals possível.Sinta-se livre para editar/corrigir as coisas que nele há.

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

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 o lançamento final do software será colocado em um Zedboard Avnet (que possui portas para displays e consoles, mas não são necessários/desejados para o último lançamento). Então, durante o desenvolvimento, vou usar printf Para visualizar as mensagens de status e erro, mas quando terminar, não quero nada impresso. Aqui está o que eu fiz:

versão.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

Dentro version.c Vou definir os 2 protótipos de função presentes em version.h

versão.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 em version.h Como

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

Quando for referenciado no aplicativo, ele começará a executar onde quer que esteja apontando, o que ainda não foi definido.

Dentro version.c, observe no board_init()função onde zprintf recebe uma função única (cuja assinatura da função corresponde), dependendo da versão que é definida em version.h

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

ou

zprintf = &noprint; Zprintf apenas retorna e não executará código desnecessário

Executar o código será 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 usará printf Se no modo de depuração, ou não faça nada se no modo de liberação. Isso é muito mais fácil do que passar por todo o projeto e comentar ou excluir o código. Tudo o que preciso fazer é mudar a versão em version.h E o código fará o resto!

O ponteiro da função é geralmente definido por typedef, e usado como Valor Param & Return.

As respostas acima já explicaram muito, apenas dito 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;
}

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 tem duas rotinas, qsort e bsearch, que levam um ponteiro a uma função que é chamada para comparar dois itens sendo classificados; Isso permite que você classifique ou pesquise, respectivamente, qualquer coisa, com base em qualquer critério que desejar usar.

Um exemplo muito básico, se houver uma função chamada print(int x, int y) que por sua vez pode exigir chamar uma função (seja add() ou sub(), que são do mesmo tipo) então o que faremos, adicionaremos um argumento de ponteiro de função ao print() 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

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

A única exceção que eu posso pensar em tratar o ponteiro de função de apontar para algo diferente de um único valor.Fazendo ponteiro aritmética aumentando ou diminuindo um ponteiro de função ou adicionar/subtrair um deslocamento para um ponteiro de função não é realmente de alguma utilidade como um ponteiro de função apenas aponta para uma única coisa, o ponto de entrada de uma função.

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

A declaração de um ponteiro de função variável deve especificar o mesmo tipo de informação como uma declaração de função para que o compilador de C para fazer os tipos de verificações que ele normalmente faz.Se você não especificar uma lista de parâmetro na declaração/definição do ponteiro de função, o compilador C não será capaz de verificar a utilização de parâmetros.Há casos em que essa falta de verificação pode ser útil, no entanto, apenas 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.

Os dois primeiros declararations são um pouco semelhantes em que:

  • func é uma função que leva um int e um char * e retorna um int
  • pFunc é um ponteiro de função para o qual é atribuído o endereço de uma função que leva um int e um char * e retorna um int

Assim, a partir do exposto acima, nós poderíamos ter uma linha de origem na qual o endereço da função func() é atribuído para a variável apontador de função pFunc como no pFunc = func;.

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

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 Utilização

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 a variável comprimento de listas de parâmetro na definição de um ponteiro de função.

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

Ou você pode não especificar um parâmetro de lista em todos os.Isso pode ser útil, mas elimina a oportunidade para que o compilador de C para realizar verificações na lista de argumento fornecido.

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);

C estilo Lança

Você pode usar C estilo lança com ponteiros de função.No entanto, esteja ciente de que um compilador C pode ser lax sobre cheques 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.

Compare Ponteiro de Função para a Igualdade

Você pode verificar que um ponteiro de função é igual a uma função particular de endereços usando um if instrução embora eu não sou certo como útil que seria.Outros operadores de comparação, parece ter ainda menos utilitário.

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 de cada um dos elementos de que a lista de argumentos tem diferenças, em seguida, você pode definir um ponteiro de função com o argumento da lista não especificado (não void o que significa que não há argumentos, mas apenas não especificado) algo como o seguinte, que você pode ver avisos do compilador de C.Isso também funciona para um ponteiro de função de parâmetro 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 namespace De Uma Forma Global struct com Ponteiros de Função

Você pode usar o static palavra-chave para especificar uma função cujo nome é o escopo do arquivo e, em seguida, atribuir isso a uma variável global como uma forma de proporcionar algo semelhante para o namespace a funcionalidade de C++.

Em um arquivo de cabeçalho definir uma estrutura que vai ser o nosso espaço de nomes juntamente com uma variável global que o utiliza.

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};

Este seria, então, ser usado ao especificar o nome completo da estrutura global da variável e o nome do membro para acessar a função.O const o modificador é usado no mundial de modo que ela não pode ser alterada por acidente.

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

Áreas de aplicação de Ponteiros de Função

Uma DLL de biblioteca de componente poderia fazer algo semelhante ao estilo C namespace abordagem em que uma determinada biblioteca de interface é solicitado a partir de um método de fábrica em uma biblioteca de interface que suporta a criação de um struct contém ponteiros de função..Esta biblioteca de interface de cargas requerido versão da DLL, cria uma estrutura, com as necessárias ponteiros de função e, em seguida, retorna a estrutura para o solicitante do chamador para o 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 poderia ser usado como em:

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

A mesma abordagem pode ser utilizada para definir um resumo camada de hardware para o código que utiliza um modelo específico de hardware subjacente.Ponteiros de função são preenchidos com hardware específico de funções de uma fábrica para fornecer o hardware específico funcionalidade que implementa funções especificadas no resumo modelo de hardware.Isso pode ser usado para fornecer um resumo camada de hardware utilizados pelo software que chama uma fábrica de função, a fim de obter o hardware específico de interface de função, em seguida, usa a função ponteiros fornecidos para executar ações para o hardware subjacente, sem necessidade de saber detalhes de implementação sobre o destino específico.

Ponteiros de função para criar os Delegados, os Manipuladores e Retornos de chamada

Você pode usar ponteiros de função, como uma forma de delegar algumas tarefas ou funcionalidade.O clássico exemplo em C é a comparação delegado ponteiro de função usados com as funções de biblioteca C Padrão qsort() e bsearch() para fornecer o agrupamento de ordenação para ordenar uma lista de itens ou realizar uma pesquisa binária através de uma lista ordenada de itens.A função de comparação delegado especifica o algoritmo de agrupamento utilizado na ordenação ou a pesquisa binária.

Outro uso é semelhante para a aplicação de um algoritmo para uma Biblioteca Padrão de C++ recipiente.

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 é com o GUI código-fonte em que um manipulador para um determinado evento é registrado pelo fornecimento de um ponteiro de função que, na verdade, é chamada quando o evento acontece.A Microsoft estrutura MFC com a sua mensagem de mapas usa algo semelhante para processar mensagens do Windows que são entregues a uma janela ou thread.

Assíncrona de funções que requerem um retorno de chamada são semelhantes a um processador de eventos.O usuário da função assíncrona chama a função assíncrona para iniciar alguma ação e fornece uma função de ponteiro que a função assíncrona será chamada uma vez que a ação seja concluída.Neste caso, o evento é a função assíncrona para completar sua tarefa.

Começando a partir da função do zero, possui algum endereço de memória de onde eles começam a executar. Na linguagem da montagem, eles são chamados como (ligue para "endereço de memória da função"). Agora volte para C se a função tiver um endereço de memória, eles podem ser manipulados por ponteiros em C.so pelas regras de c

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

**** nota-> As funções devem ser do mesmo tipo ****

Este programa simples ilustrará tudo.

#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");
}

enter image description hereDepois disso, vamos ver como a máquina os entende.

A área de marca vermelha está mostrando como o endereço está sendo trocado e armazenando na EAX. Então eles são uma instrução de chamada no EAX. O EAX contém o endereço desejado da função.

Como os ponteiros da função são frequentemente digitados retornos de chamada, você pode querer dar uma olhada Digite retornos de chamada seguros. O mesmo se aplica a pontos de entrada, etc. de funções que não são retornos de chamada.

C é bastante inconstante e perdoador ao mesmo tempo :)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top