Como funcionam os pointers em C?

Eu tive alguma experiência ultimamente com pointers de function em C.

Então, seguindo a tradição de responder suas próprias perguntas, decidi fazer um pequeno resumo das coisas básicas, para aqueles que precisam de um mergulho rápido no assunto.

Ponteiros de function em C

Vamos começar com uma function básica a qual estaremos apontando :

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

Primeiro, vamos definir um ponteiro para uma function que recebe 2 int retorna um int :

 int (*functionPtr)(int,int); 

Agora podemos apontar com segurança para nossa function:

 functionPtr = &addInt; 

Agora que temos um ponteiro para a function, vamos usá-lo:

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

Passar o ponteiro para outra function é basicamente o mesmo:

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

Podemos usar pointers de function em valores de retorno também (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; } 

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

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

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

Sim, o -> e a falta de um new operador é um dado morto, mas com certeza parece implicar que estamos definindo o texto de alguma class String como "hello" .

Usando pointers de function, é possível emular methods em C.

Como isso é feito?

A class String é na verdade uma struct com vários pointers de function que funcionam como uma maneira de simular methods. A seguir, uma declaração parcial da class String :

 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 methods da class String são, na verdade, pointers de function para a function declarada. Ao preparar a instância da String , a function newString é chamada para configurar os pointers de function 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 function getString que é chamada invocando o método get é definida como o seguinte:

 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 object e de ter methods que são, na verdade, parte de um object, portanto, um “self object” deve ser passado em cada chamada. (E o internal é apenas uma struct oculta que foi omitida da listview de código anterior – é uma maneira de executar a ocultação de informações, mas isso não é relevante para os pointers de function.)

Então, ao invés de ser capaz de fazer s1->set("hello"); , deve-se passar no object para realizar a ação em s1->set(s1, "hello") .

Com essa pequena explicação tendo que passar em uma referência para você fora do caminho, vamos passar para a próxima parte, que é inheritance em C.

Digamos que queremos fazer uma subclass de String , digamos um ImmutableString . Para tornar a string imutável, o método set não estará acessível, mantendo o access a get e length , e forçando o “construtor” a 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 subclasss, os methods disponíveis são mais uma vez pointers de function. Desta vez, a declaração para o método set não está presente, portanto, não pode ser chamada em um ImmutableString .

Quanto à implementação do ImmutableString , o único código relevante é a function “construtor”, 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; } 

Ao instanciar o ImmutableString , os pointers de function para os methods get e length referem-se ao método String.get e String.get , passando pela variável base que é um object String armazenado internamente.

O uso de um ponteiro de function pode obter inheritance de um método de uma superclass.

Podemos continuar com o polymorphism em C.

Se, por exemplo, quisermos mudar o comportamento do método length para retornar 0 o tempo todo na class ImmutableString por algum motivo, tudo o que deve ser feito é:

  1. Adicione uma function que servirá como o método de length substituição.
  2. Vá para o “construtor” e defina o ponteiro de function para o método de length substituição.

Adicionando um método de length substituição em ImmutableString pode ser realizado adicionando um lengthOverrideMethod :

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

Em seguida, o ponteiro de function para o método length 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 método length na class ImmutableString como a class String , agora o método length se referirá ao comportamento definido na function lengthOverrideMethod .

Devo adicionar um aviso de que ainda estou aprendendo a escrever com um estilo de programação orientado a object em C, então provavelmente há pontos que não expliquei bem, ou que podem estar fora de questão em termos de como implementar melhor OOP em C. Mas meu objective era tentar ilustrar um dos muitos usos de pointers de function.

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

  • Orientação a Objetos em C?
  • Você pode escrever código orientado a objects em C?

O guia para ser demitido: Como abusar de pointers de function no GCC em máquinas x86 compilando seu código manualmente:

  1. Retorna o valor atual no registrador EAX

     int eax = ((int(*)())("\xc3 < - This returns the value of the EAX register"))(); 
  2. Escreva uma function 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 function de 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 function 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); 

Um dos meus usos favoritos para pointers de function é como iteradores baratos e fáceis –

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

Os pointers de function tornam-se fáceis de declarar depois que você tiver os declaradores básicos:

  • id: ID : ID é um
  • Ponteiro: *D : D ponteiro para
  • Função: D() : function D tomando < parâmetros > retornando

Enquanto D é outro declarador construído usando essas mesmas regras. No final, em algum lugar, termina com ID (veja abaixo um exemplo), que é o nome da entidade declarada. Vamos tentar construir uma function tomando um ponteiro para uma function sem nada e retornando int, e retornando um ponteiro para uma function tomando um caractere e retornando int. Com tipo-defs é assim

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

Como você vê, é muito fácil criá-lo usando typedefs. Sem typedefs, não é difícil nem com as regras do declarador acima, aplicadas de forma consistente. Como você vê, eu perdi a parte que o ponteiro aponta e a coisa que a function retorna. Isso é o que aparece à esquerda da declaração e não é de interesse: ela é adicionada no final, caso já tenha sido construído o declarador. Vamos fazer isso. Construindo consistentemente, primeiro wordy - mostrando a estrutura usando [ e ] :

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

Como você pode ver, é possível 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 certa (folhas) e trabalhando até o identificador. A outra maneira é de cima para baixo, começando pelo identificador, indo até as folhas. Eu mostrarei as duas maneiras.

Debaixo para cima

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

 D1(char); 

Inseriu o parâmetro char diretamente, já que é trivial. Adicionando um ponteiro ao declarator, substituindo D1 por *D2 . Note que temos que envolver os parênteses em torno de *D2 . Isso pode ser conhecido observando-se a precedência do *-operator e do operador de chamada de function () . Sem nossos parênteses, o compilador iria lê-lo como *(D2(char p)) . Mas isso não seria mais uma substituição simples de D1 por *D2 , é claro. Parênteses são sempre permitidos em torno de declaradores. Então você não faz nada de errado se você adicionar muito deles, na verdade.

 (*D2)(char); 

O tipo de retorno está completo! Agora, vamos replace D2 pela function do declarador de function , retornando , que é D3() qual estamos agora.

 (*D3())(char) 

Note que nenhum parênteses são necessários, já que queremos que D3 seja um declarador de funções e não um declarator de ponteiro desta vez. Ótimo, só resta os parâmetros para isso. O parâmetro é feito exatamente da mesma forma que fizemos o tipo de retorno, apenas com char substituído por void . Então eu vou copiá-lo:

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

Eu substitui D2 por ID1 , já que terminamos com esse parâmetro (já é um ponteiro para uma function - não há necessidade de outro declarador). ID1 será o nome do parâmetro. Agora, eu disse acima no final que se acrescenta o tipo que todos os declaradores modificam - o que aparece à esquerda de cada declaração. Para funções, isso se torna o tipo de retorno. Para pointers o apontado para o tipo etc ... É interessante quando escrito o tipo, ele aparecerá na ordem oposta, na extrema direita 🙂 De qualquer forma, substituí-lo produz a declaração completa. Ambas as vezes int claro.

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

Eu chamei o identificador da function ID0 nesse exemplo.

Careca

Isso começa no identificador no canto esquerdo da descrição do tipo, envolvendo esse declarador enquanto percorremos o caminho à direita. Comece com a function tomando < parameters > retornando

 ID0() 

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

 *ID0() 

Então a próxima coisa foi a function de retornar < parameters > . O parâmetro é um caractere simples, então o colocamos imediatamente, já que é realmente trivial.

 (*ID0())(char) 

Observe os parênteses que adicionamos, pois queremos novamente que o * ligado primeiro e, em seguida, o (char) . Caso contrário, ele iria ler a function tomando < parameters > retornando a function .... Não, as funções de retorno de funções não são permitidas.

Agora só precisamos colocar < parameters > . Eu mostrarei uma versão curta da derivação, já que eu acho que você já tem a ideia de como fazer isso.

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

Basta colocar int antes dos declaradores como fizemos com bottom-up, e estamos acabados

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

O bom

É de baixo para cima ou de cima para baixo melhor? Estou acostumado a fazer o bottom-up, mas algumas pessoas podem estar mais confortáveis ​​com o top-down. É uma questão de gosto eu acho. Aliás, se você aplicar todos os operadores nessa declaração, você acabará recebendo um int:

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

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

Espero que você tenha gostado deste pequeno tutorial! Agora podemos nos ligar a isso quando as pessoas se perguntam sobre a syntax estranha das funções. Eu tentei colocar o mínimo possível de internals em C. Sinta-se à vontade para editar / corrigir coisas nele.

Outro bom uso para os pointers de function:
Alternando entre versões sem dor

Eles são muito úteis para usar quando você quer 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 elas não são necessárias / desejadas para o console). último lançamento). Então, durante o desenvolvimento, usarei o printf para visualizar mensagens de status e 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  #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.c eu vou definir os protótipos de duas funções presentes em version.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 de function é prototipado em version.h as

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

Quando é referenciada na aplicação, ela começará a ser executada onde quer que esteja apontando, o que ainda precisa ser definido.

Em version.c , observe na function board_init() onde zprintf é atribuído a uma function exclusiva (cuja assinatura de function corresponde) dependendo da versão definida em version.h

zprintf = &printf; zprintf chama printf para fins de debugging

ou

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

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

mainProg.c

 #include "version.h" #include  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 estiver no modo de debugging, ou não fará nada se estiver no modo de liberação. Isso é muito mais fácil do que percorrer todo o projeto e comentar ou excluir o código. Tudo o que preciso fazer é alterar a versão em version.h e o código fará o resto!

O ponteiro de function é geralmente definido por typedef e usado como param & return value,

Acima respostas já explicadas muito, acabei de dar um exemplo completo:

 #include  #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 ∑ } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p = ∑ // 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 os pointers de function em C é chamar uma function 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 para uma function que é chamada para comparar dois itens sendo classificados; isso permite classificar ou pesquisar, respectivamente, qualquer coisa, com base em qualquer critério que você deseje usar.

Um exemplo muito básico, se existe uma function chamada print (int x, inty) que por sua vez pode requerer chamar a function add () ou sub () que são de tipos similares então o que faremos, nós adicionaremos uma function argumento de ponteiro para a function print () como mostrado abaixo:

 int add() { return (100+10); } int sub() { return (100-10); } void print(int x, int y, int (*func)()) { printf("value is : %d", (x+y+(*func)())); } int main() { int x=100, y=200; print(x,y,add); print(x,y,sub); return 0; } 

A partir da function zero tem Algum Endereço de Memória De Onde eles começam a executar. Em Assembly Language Eles são chamados como (chamar “endereço de memory da function”). Agora, voltem para C Se a function tiver um endereço de memory, então eles podem ser manipulados por Ponteiros em C. Assim, pelas regras de C

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

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

Este programa simples irá ilustrar cada coisa.

 #include 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 aqui After That permite ver como a máquina entende Them.Glimpse da instrução da máquina do programa acima na arquitetura de 32 bits.

A área da 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 function

Como os pointers de function geralmente são chamados de retornos de chamada, convém dar uma olhada nos retornos de chamada de tipo seguro . O mesmo se aplica a pontos de input, etc, de funções que não são retornos de chamada.

C é bastante inconstante e perdoador ao mesmo tempo 🙂

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

A única exceção que consigo pensar é tratar o ponteiro de function como apontando para algo diferente de um único valor. Fazer aritmética de ponteiro incrementando ou decrementando um ponteiro de function ou adicionando / subtraindo um deslocamento a um ponteiro de function não é realmente de utilidade alguma, já que um ponteiro de function aponta apenas para uma única coisa, o ponto de input de uma function.

O tamanho de uma variável de ponteiro de function, o número de bytes ocupados pela variável, pode variar dependendo da arquitetura subjacente, por exemplo, x32 ou x64 ou qualquer outra coisa.

A declaração para uma variável de ponteiro de function precisa especificar o mesmo tipo de informação que uma declaração de function para que o compilador C faça os tipos de verificações que ela normalmente faz. Se você não especificar uma lista de parâmetros na declaração / definição do ponteiro de function, 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 em que:

  • func é uma function que pega um int e um char * e retorna um int
  • pFunc é um ponteiro de function ao qual é atribuído o endereço de uma function que recebe um int e um char * e retorna um int

Assim, a partir do acima, poderíamos ter uma linha de origem na qual o endereço da function func() é atribuído à variável de ponteiro de function pFunc como em pFunc = func; .

Observe a syntax usada com uma declaração / definição de ponteiro de function em que os parênteses são usados ​​para superar as regras de precedência natural do 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 uso diferentes

Alguns exemplos de uso de um ponteiro de function:

 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 function.

 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 para o compilador C 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 modelos de estilo C com pointers de function. 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 o ponteiro de function à igualdade

Você pode verificar se um ponteiro de function é igual a um endereço de function específico usando uma instrução if , embora não tenha certeza de como isso seria útil. 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 pointers de function

E se você quiser ter uma matriz de pointers de function cada um dos elementos dos quais a lista de argumentos tem diferenças, então você pode definir um ponteiro de function com a lista de argumentos não especificada (não void que significa nenhum argumento, mas apenas não especificado) você pode ver avisos do compilador C. Isso também funciona para um parâmetro de ponteiro de function para uma function:

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

namespace estilo C Usando struct global com pointers de function

Você pode usar a palavra-chave static para especificar uma function cujo nome é o escopo do arquivo e, em seguida, atribuí-la a uma variável global como uma maneira de fornecer algo semelhante à funcionalidade de namespace do C ++.

Em um arquivo de header, defina uma struct que será nosso namespace junto com uma variável global que a 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}; 

This would then be used by specifying the complete name of global struct variable and member name to access the function. The const modifier is used on the global so that it can not be changed by accident.

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

Application Areas of Function Pointers

A DLL library component could do something similar to the C style namespace approach in which a particular library interface is requested from a factory method in a library interface which supports the creation of a struct containing function pointers.. This library interface loads the requested DLL version, creates a struct with the necessary function pointers, and then returns the struct to the requesting caller for use.

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

and this could be used as in:

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

The same approach can be used to define an abstract hardware layer for code that uses a particular model of the underlying hardware. Function pointers are filled in with hardware specific functions by a factory to provide the hardware specific functionality that implements functions specified in the abstract hardware model. This can be used to provide an abstract hardware layer used by software which calls a factory function in order to get the specific hardware function interface then uses the function pointers provided to perform actions for the underlying hardware without needing to know implementation details about the specific target.

Function Pointers to create Delegates, Handlers, and Callbacks

You can use function pointers as a way to delegate some task or functionality. The classic example in C is the comparison delegate function pointer used with the Standard C library functions qsort() and bsearch() to provide the collation order for sorting a list of items or performing a binary search over a sorted list of items. The comparison function delegate specifies the collation algorithm used in the sort or the binary search.

Another use is similar to applying an algorithm to a C++ Standard Template Library container.

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

Another example is with GUI source code in which a handler for a particular event is registered by providing a function pointer which is actually called when the event happens. The Microsoft MFC framework with its message maps uses something similar to handle Windows messages that are delivered to a window or thread.

Asynchronous functions that require a callback are similar to an event handler. The user of the asynchronous function calls the asynchronous function to start some action and provides a function pointer which the asynchronous function will call once the action is complete. In this case the event is the asynchronous function completing its task.

function pointers are useful in many situations, eg:

  • COM objects members are pointer to function ag: This->lpVtbl->AddRef(This); AddRef is a pointer to a function.
  • function callback, for example a user defined function to compares two variables to be passed as a callback to a special sort function.
  • very useful for plugin implementation and application SDK.