C中的函数指针是如何工作的?

我最近在C中使用函数指针有一些经验。

因此,按照回答您自己问题的传统,我决定对基础知识进行一个小总结,供那些需要快速深入了解该主题的人使用。

944708 次浏览

C中的函数指针

让我们从一个基本函数开始,我们将是指向

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

首先,让我们定义一个指向接收2int并返回int的函数的指针:

int (*functionPtr)(int,int);

现在我们可以安全地指出我们的函数:

functionPtr = &addInt;

现在我们有了一个指向函数的指针,让我们使用它:

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

将指针传递给另一个函数基本相同:

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

我们也可以在返回值中使用函数指针(试着跟上,它会变得混乱):

// 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 intint (*functionFactory(int n))(int, int) {printf("Got parameter %d", n);int (*functionPtr)(int,int) = &addInt;return functionPtr;}

但是使用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;}

我最喜欢的函数指针的用途之一是廉价和简单的迭代器-

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

C中的函数指针可用于在C中执行面向对象的编程。

例如,以下行是用C编写的:

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

是的,->和缺少new运算符是一个死胡同,但它确实似乎暗示我们将一些String类的文本设置为"hello"

通过使用函数指针,可以在C中模拟方法

这是如何实现的?

String类实际上是一个struct,带有一堆函数指针,作为模拟方法的一种方式。以下是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();

可以看出,String类的方法实际上是指向声明函数的函数指针。在准备String的实例时,调用newString函数是为了设置指向各自函数的函数指针:

String newString(){String self = (String)malloc(sizeof(struct String_Struct));
self->get = &getString;self->set = &setString;self->length = &lengthString;
self->set(self, "");
return self;}

例如,通过调用get方法调用的getString函数定义如下:

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

可以注意到的一点是,没有对象实例的概念,也没有方法实际上是对象的一部分,所以每次调用都必须传入一个“self对象”。(并且internal只是一个隐藏的struct,它在前面的代码列表中被省略了——它是一种执行信息隐藏的方式,但这与函数指针无关。)

因此,不是能够执行s1->set("hello");,而是必须传入对象以执行s1->set(s1, "hello")上的操作。

有了这个小解释,必须传递一个对自己的引用,我们将移动到下一部分,即C中的继承

假设我们想创建String的子类,比如ImmutableString。为了使字符串不可变,set方法将无法访问,同时保持对getlength的访问,并强制“构造函数”接受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);

基本上,对于所有子类,可用的方法再次是函数指针。这一次,set方法的声明不存在,因此,它不能在ImmutableString中调用。

至于ImmutableString的实现,唯一相关的代码是“构造函数”函数,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;}

在实例化ImmutableString时,指向getlength方法的函数指针实际上引用了String.getString.length方法,通过遍历base变量,该变量是内部存储的String对象。

使用函数指针可以实现方法从超类的继承。

我们可以继续C中的多态

例如,如果我们出于某种原因想要更改length方法的行为以在ImmutableString类中始终返回0,那么所要做的就是:

  1. 添加一个将用作覆盖length方法的函数。
  2. 转到“构造函数”并将函数指针设置为覆盖length方法。

ImmutableString中添加覆盖length方法可以通过添加lengthOverrideMethod来执行:

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

然后,构造函数中length方法的函数指针连接到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;}

现在,ImmutableString类中的length方法不再具有与String类相同的行为,现在length方法将引用lengthOverrideMethod函数中定义的行为。

我必须补充一个免责声明,我仍然在学习如何用C语言编写面向对象的编程风格,所以可能有一些点我没有解释好,或者可能只是在如何最好地在C中实现OOP方面偏离了目标。

有关如何在C中执行面向对象编程的更多信息,请参阅以下问题:

一旦你有了基本的声明器,函数指针就变得容易声明:

  • id:IDid是一个
  • 指针:*Dd指针指向
  • 功能:D(<parameters>)D函数#1参数#2返回

而D是另一个使用相同规则构建的声明符。最后,在某个地方,它以ID结尾(参见下面的示例),这是声明实体的名称。让我们尝试构建一个函数,它接受一个指针指向一个不接受任何东西并返回int的函数,并返回一个指针指向一个接受char并返回int的函数。使用type-def是这样的

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

如你所见,使用typedef构建它很容易。没有typedef,使用上述声明器规则并一致地应用也不难。如你所见,我错过了指针指向的部分,以及函数返回的内容。这是出现在声明最左边的内容,并不感兴趣:如果已经构建了声明器,它会添加在最后。让我们这样做。始终如一地构建它,首先冗长-显示使用[]的结构:

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

如您所见,可以通过一个接一个地附加声明符来完全描述一个类型。构造可以通过两种方式完成。一种是自下而上,从非常正确的东西(叶子)开始,一直到标识符。另一种是自上而下,从标识符开始,一直到叶子。我将展示两种方法。

自下而上

构造从右边的东西开始:返回的东西,这是接受char的函数。为了保持声明符的区别,我将对它们进行编号:

D1(char);

直接插入char参数,因为它很简单。通过将D1替换为*D2来添加指向声明符的指针。请注意,我们必须在*D2周围包装括号。这可以通过查找*-operator和函数调用操作符()的优先级来知道。如果没有括号,编译器会将其读取为*(D2(char p))。但这当然不再是将D1替换为*D2。括号总是允许在声明符周围。所以实际上,如果添加太多括号,也不会出错。

(*D2)(char);

返回类型完成!现在,让我们用函数声明符函数#1返回替换D2,这是我们现在的D3(<parameters>)

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

请注意,不需要括号,因为我们想要D3这次是函数声明器而不是指针声明器。太好了,唯一剩下的就是它的参数。参数的操作与我们对返回类型的操作完全相同,只是char替换为void。所以我会复制它:

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

我已经将D2替换为ID1,因为我们已经完成了该参数(它已经是指向函数的指针-不需要另一个声明器)。ID1将是参数的名称。现在,我在上面告诉过,最后一个添加了所有声明器修改的类型-出现在每个声明的最左侧。对于函数,这成为返回类型。对于指向类型的指针等……有趣的是,当写下类型时,它会以相反的顺序出现,在最右边:)无论如何,替换它会产生完整的声明。当然,两次int

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

我在该示例中调用了函数ID0的标识符。

自上而下

这从类型描述中最左边的标识符开始,当我们穿过右边时包装该声明符。从返回#0参数#1的函数开始

ID0(<parameters>)

描述中的下一件事(在“返回”之后)是指针指向。让我们合并它:

*ID0(<parameters>)

接下来是函数取#0参数#1返回。参数是一个简单的char,所以我们马上把它放进去,因为它真的很琐碎。

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

注意我们添加的括号,因为我们再次希望*先绑定,然后先绑定(char)。否则它将读取函数#2参数#3返回函数…。不,甚至不允许返回函数的函数。

现在我们只需要输入<参数>。我将展示一个简短的派生版本,因为我想你现在已经知道如何做到这一点了。

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

只需像我们使用自下而上一样将int放在声明符之前,我们就完成了

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

好东西

自下而上还是自上而下更好?我习惯自下而上,但是有些人可能更喜欢自上而下。我认为这是一个品味问题。顺便说一句,如果你应用该声明中的所有运算符,你最终会得到一个int:

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

这是C中声明的一个很好的属性:声明断言,如果在使用标识符的表达式中使用这些运算符,则它会产生最左边的类型。这也适用于数组。

希望你喜欢这个小教程!现在,当人们想知道函数的奇怪声明语法时,我们可以链接到这个。我试着尽可能少地添加C内部元素。随意编辑/修复其中的东西。

由于函数指针通常是类型回调,因此您可能希望查看类型安全回调。这同样适用于非回调函数的入口点等。

C是相当善变和原谅在同一时间:)

被解雇指南:如何通过手动编译代码来滥用x86机器上GCC中的函数指针:

这些字符串文字是32位x86机器代码的字节。0xC3一条x86#1指令

您通常不会手动编写这些,您可以使用汇编语言编写,然后使用像nasm这样的汇编器将其组装成一个平面二进制文件,您可以将其hexuck转换为C字符串文字。

  1. 返回EAX寄存器上的当前值

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
  2. Write a swap function

    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. Write a for-loop counter to 1000, calling some function each time

    ((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. You can even write a recursive function that counts to 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);

Note that compilers place string literals in the .rodata section (or .rdata on Windows), which is linked as part of the text segment (along with code for functions).

The text segment has Read+Exec permission, so casting string literals to function pointers works without needing mprotect() or VirtualProtect() system calls like you'd need for dynamically allocated memory. (Or gcc -z execstack links the program with stack + data segment + heap executable, as a quick hack.)


To disassemble these, you can compile this to put a label on the bytes, and use a disassembler.

// at global scopeconst 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";

使用gcc -c -m32 foo.c编译并使用objdump -D -rwC -Mintel反汇编,我们可以获得汇编,并发现此代码通过破坏EBX(调用保留寄存器)违反了ABI,并且通常效率低下。

00000000 <swap>:0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *aa:   8b 1b                   mov    ebx,DWORD PTR [ebx]c:   31 c3                   xor    ebx,eax                # pointless xor-swape:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers10:   31 c3                   xor    ebx,eax12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]1c:   89 19                   mov    DWORD PTR [ecx],ebx1e:   c3                      ret
not shown: the later bytes are ASCII text documentationthey're not executed by the CPU because the ret instruction sends execution back to the caller

此机器代码(可能)在Windows、Linux、OS X等上的32位代码中工作:所有这些操作系统上的默认调用约定都在堆栈上传递参数,而不是在寄存器中更有效。但是EBX在所有正常调用约定中都保留调用,因此将其用作暂存寄存器而不保存/恢复它很容易使调用者崩溃。

函数指针的另一个很好的用途:
轻松地在版本之间切换

当你在不同的时间,或不同的开发阶段想要不同的功能时,它们非常方便。例如,我正在一台有控制台的主机上开发一个应用程序,但软件的最终版本将放在Avnet ZedBoard上(它有显示器和控制台的端口,但最终版本不需要/想要它们)。所以在开发过程中,我会使用printf来查看状态和错误消息,但是当我完成后,我不希望打印任何东西。这是我所做的:

版本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 screenvoid (* 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

version.c中,我将定义version.h中存在的2个函数原型

版本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_VERSIONzprintf = &printf;#else// Defined below this functionzprintf = &noprintf;#endif}
/*****************************************************************************//*** @name noprintf** simply returns with no actions performed** @return   None******************************************************************************/void noprintf(const char* c, ...){return;}

注意函数指针是如何在#0中原型化为

#1

当它在应用程序中被引用时,它将开始执行它指向的任何地方,这尚未定义。

version.c中,注意在board_init()函数中,zprintf被分配了一个唯一的函数(其函数签名匹配),这取决于version.h中定义的版本

#4 zprintf调用printf进行调试

#5 zprintf只是返回并且不会运行不必要的代码

运行代码将如下所示:

主项目c

#include "version.h"#include <stdlib.h>int main(){// Must run board_init(), which assigns the function// pointer to an actual functionboard_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;}

如果处于调试模式,上面的代码将使用printf,如果处于发布模式,则什么也不做。这比浏览整个项目并注释掉或删除代码要容易得多。我需要做的就是更改version.h中的版本,其余的代码就会完成!

从头开始函数有一些内存地址从他们开始执行的地方开始。在汇编语言中,它们被称为(调用“函数的内存地址”)。现在回到C如果函数有内存地址,那么它们可以由C中的指针操作。所以根据C的规则

1.首先需要声明一个指向函数的指针2.传递所需函数的地址

****注意->函数应该是相同的类型****

这个简单的程序将说明一切。

#include<stdio.h>void (*print)() ;//Declare a  Function Pointersvoid sayhello();//Declare The Function Whose Address is to be passed//The Functions should Be of Same Typeint main(){print=sayhello;//Addressof sayhello is assigned to printprint();//print Does A call To The Functionreturn 0;}
void sayhello(){printf("\n Hello World");}

在此处输入图像描述之后让我们看看机器如何理解主题。

红色标记区域显示地址是如何在eax中交换和存储的。然后他们是eax上的调用指令。eax包含函数所需的地址。

函数指针通常由typedef定义,用作参数和返回值。

上面的答案已经解释了很多,我只是举一个完整的例子:

#include <stdio.h>
#define NUM_A 1#define NUM_B 2
// define a function pointer typetypedef int (*two_num_operation)(int, int);
// an actual standalone functionstatic 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 pointerprintf("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;}

函数指针在C中的一个重要用途是调用在运行时选择的函数。例如,C运行时库有两个例程,#0#1,它们接受一个指向被调用的函数的指针来比较正在排序的两个项目;这允许您根据您希望使用的任何条件分别对任何内容进行排序或搜索。

一个非常基本的例子,如果有一个名为print(int x, int y)的函数,它可能需要调用一个函数(add()sub(),它们的类型相同),那么我们将做什么,我们将向print()函数添加一个函数指针参数,如下所示:

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

输出是:

值为:410
值为:390

函数指针是包含函数地址的变量。由于它是一个指针变量,尽管具有一些受限制的属性,因此您可以像使用数据结构中的任何其他指针变量一样使用它。

我能想到的唯一例外是将函数指针视为指向单个值以外的东西。通过递增或递减函数指针或添加/减去函数指针的偏移量来执行指针算术实际上没有任何用处,因为函数指针仅指向单一事物,函数的切入点。

函数指针变量的大小,变量占用的字节数,可能会因底层架构而异,例如x32或x64或其他什么。

函数指针变量的声明需要指定与函数声明相同类型的信息,以便C编译器进行通常的检查。如果您在函数指针的声明/定义中未指定参数列表,C编译器将无法检查参数的使用。在某些情况下,这种缺乏检查可能是有用的,但请记住,安全网已经被移除。

一些例子:

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.

前两个声明有点相似:

  • func是一个接受intchar *并返回int的函数
  • pFunc是一个函数指针,它被分配了一个函数的地址,该函数接受intchar *并返回int

因此,从上面我们可以有一个源行,其中函数func()的地址被分配给函数指针变量pFunc,如pFunc = func;所示。

注意与函数指针声明/定义一起使用的语法,其中括号用于克服自然运算符优先级规则。

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

几种不同的用法示例

使用函数指针的一些示例:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variableint (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointersint (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variablestruct {                             // declare a struct that contains a function pointerint x22;int (*pFunc)(int a, char *pStr);} thing = {0, func};                 // assign values to the struct variablechar * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argumentchar * (*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

您可以在函数指针的定义中使用可变长度的参数列表。

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

或者您根本无法指定参数列表。这可能很有用,但它消除了C编译器对提供的参数列表执行检查的机会。

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

C风格演员阵容

您可以将C风格的强制转换与函数指针一起使用。但是请注意,C编译器可能对检查松懈或提供警告而不是错误。

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

比较函数指针到相等

您可以使用if语句检查函数指针是否等于特定函数地址,尽管我不确定这有多有用。其他比较运算符似乎更没有实用性。

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

函数指针数组

如果你想拥有一个函数指针数组,其中每个元素的参数列表都有差异,那么你可以定义一个参数列表未指定的函数指针(不是void,这意味着没有参数,只是未指定),类似于以下内容,尽管你可能会看到来自C编译器的警告。这也适用于函数的函数指针参数:

int(*p[])() = {       // an array of function pointersfunc1, 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 indexfor (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {func4(a, b, 0, p[i]);}// iterate over the array elements using a pointerfor (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.}

C风格namespace使用全局struct和函数指针

您可以使用static关键字指定一个名为file范围的函数,然后将其分配给一个全局变量,作为提供类似于C++namespace功能的一种方式。

在头文件中定义一个结构,该结构将成为我们的命名空间以及使用它的全局变量。

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

然后在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};

然后通过指定全局结构变量的完整名称和成员名称来使用它来访问函数。const修饰符用于全局,因此不能意外更改它。

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

功能指针的应用领域

DLL库组件可以做类似于C风格namespace方法的事情,其中从库接口中的工厂方法请求特定的库接口,该库接口支持创建包含函数指针的struct…此库接口加载请求的DLL版本,创建具有必要函数指针的结构,然后将结构返回给请求调用者以供使用。

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

这可以用作:

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

同样的方法可用于为使用底层硬件的特定模型的代码定义抽象硬件层。函数指针由工厂用硬件特定函数填充,以提供实现抽象硬件模型中指定的功能的硬件特定功能。这可用于提供软件使用的抽象硬件层,该层调用工厂函数以获取特定硬件功能接口,然后使用提供的函数指针为底层硬件执行操作,而无需知道有关特定目标的实现细节。

用于创建委托、处理程序和回调的函数指针

您可以使用函数指针来委托某些任务或功能。C中的经典示例是与标准C库函数qsort()bsearch()一起使用的比较委托函数指针,以提供排序顺序,用于对项目列表进行排序或对排序后的项目列表执行二进制搜索。比较函数委托指定排序或二进制搜索中使用的排序算法。

另一个用途类似于将算法应用于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);

另一个例子是GUI源代码,其中通过提供一个在事件发生时实际调用的函数指针来注册特定事件的处理程序。Microsoft MFC框架及其消息映射使用类似于处理传递到窗口或线程的Windows消息的东西。

需要回调的异步函数类似于事件处理程序。异步函数的用户调用异步函数来启动某些操作,并提供一个函数指针,一旦操作完成,异步函数将调用该指针。在这种情况下,事件是异步函数完成其任务。

指向函数的指针很有用,因为正如“C编程语言”一书所说,C中的函数不是变量。这意味着

// Say you have add functionint add(int x, int y){return x + y;}
// Say you have another add functionint another_add(int x, int y){return y + x;}

int main(){// Although the types of another_add and add are same// You can't doanother_add = add    
// You have a compute function that takes a function of int's signatureint (*compute)(int, int);   
// You won't even be able to pass functions to other functions// (Although when you do, C is just passing the pointer to that function)// So, compute(add) is really compute(&add)// But you can create a pointer to functions that are variables// you can assign to and/or pass to other functions
int (*operation)(int, int);// Now you can dooperation = &add;// You could also do, the following to do the same thing// When a function is passed in right hand side of assignment,// C knows that you mean pointer, and you don't need explicit &operation = add;}

类似地,数组在C中也不是变量。您可以制作一个类似的示例并进行测试。