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

我最近在C语言中使用函数指针

因此,继续回答你自己的问题的传统,我决定对那些需要快速进入主题的人做一个基本的小结。

C中的函数指针

我们先来看一个我们将要指出的基本function:

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

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

 int (*functionPtr)(int,int); 

现在我们可以安全地指向我们的function:

 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 int int (*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; } 

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

例如,下面的行用C写成:

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

是的, ->和缺less一个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; } 

有一件事可以注意到,没有对象实例的概念,并且实际上有方法是对象的一部分,所以每个调用都必须传入一个“自我对象”。 (而internal只是一个隐藏的struct ,它在代码清单中被忽略了 – 它是一种执行信息隐藏的方法,但与函数指针无关。)

所以,而不是能够做s1->set("hello"); ,必须通过对象来执行对s1->set(s1, "hello")

有了这个小的解释,我们就不得不通过一个引用来引用自己,我们将转到下一个inheritanceC的部分

假设我们想要创build一个String的子类,比如一个ImmutableString 。 为了使string不可变, 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方法的函数指针通过遍历basevariables(这是一个内部存储的String对象)实际上引用String.getString.length方法。

函数指针的使用可以实现父类中方法的inheritance。

我们可以继续在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; } 

现在,现在length方法将引用lengthOverrideMethod函数中定义的行为,而不是像ImmutableString类中的length方法那样具有相同的行为。

我必须添加一个免责声明,我仍然在学习如何用C编写一个面向对象的编程风格,所以可能有点我没有解释得很好,或者可能只是在如何最好地实现面向对象在C中,但我的目的是试图说明函数指针的许多用途之一。

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

  • 面向对象的C?
  • 你能用C编写面向对象的代码吗?

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

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

     int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))(); 
  2. 写一个交换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. 写一个for循环计数器到1000,每次调用一些函数

     ((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. 你甚至可以写一个recursion函数计数到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); 

我最喜欢使用函数指针的一个便宜又简单的迭代器 –

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

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

  • ID: IDID是一个
  • 指针: *DD指针
  • function: D(<parameters>)D函数以<参数>返回

而D是使用这些相同规则构build的另一个声明符。 最后,在某个地方,以ID结尾(见下面的例子),这是所声明的实体的名称。 让我们尝试构build一个函数,它指向一个没有任何东西并返回int的函数,并返回一个指向一个函数的指针,并返回一个int值。 typesdefs是这样的

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

正如你所看到的,使用typedef来构build它非常容易。 如果没有使用typedef,那么使用上面的声明规则并不难,一致的应用。 正如你所看到的,我错过了指针指向的部分以及函数返回的东西。 这就是声明最左边的内容,并不重要:如果已经build立了声明器,则在最后添加它。 我们来做吧 一贯地build立它,首先罗嗦 – 显示使用[]的结构:

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

正如你所看到的,可以通过一个接一个地追加声明符来完全描述一个types。 build设可以通过两种方式完成。 一个是自下而上,从正确的事情(叶)开始,一直到标识符为止。 另一种方式是自上而下,从标识符开始,一路向下。 我将展示两种方式。

自下而上

构造从右边的东西开始:返回的东西,这是使用char的函数。 为了保持不同的声明,我要给他们编号:

 D1(char); 

直接插入char参数,因为它是微不足道的。 通过用*D2replaceD1来添加一个指向声明符的指针。 请注意,我们必须围绕*D2包围括号。 通过查找*-operator和函数调用操作符()的优先级可以知道这一点。 没有我们的括号,编译器会把它读为*(D2(char p)) 。 但是,这当然不会是D1的简单replace。 圆括号总是允许在宣言者身边。 所以,如果你真的添加了太多的东西,那么你不会犯任何错误。

 (*D2)(char); 

返回types完成! 现在,让我们用函数声明符函数replaceD2 ,然后使用<parameters>返回值 ,这就是我们现在使用的D3(<parameters>)

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

请注意,不需要括号,因为我们希望 D3这次是一个函数声明符而不是一个指针声明符。 很好,唯一剩下的就是它的参数。 这个参数和我们完成返回types完全一样,只是用charreplace成void 。 所以我会复制它:

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

我已经用ID1replace了D2 ,因为我们完成了这个参数(它已经是一个指向函数的指针 – 不需要另一个声明符)。 ID1将是参数的名称。 现在,我在上面告诉了所有那些声明者修改的types – 每个声明最左边的那个types。 对于函数来说,这将成为返回types。 对于指向types的指针等等…当写下types时它会很有趣,它会以相反的顺序出现在正确的位置:)无论如何,replace它会产生完整的声明。 当然两次都是。

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

在这个例子中,我已经调用了函数ID0的标识符。

自上而下

这从types描述的最左边的标识符开始,当我们走过右边的时候,包装这个声明符。 从< parameters >返回的函数开始

 ID0(<parameters>) 

描述中的下一个东西(在“返回”之后)是指针 。 让我们把它纳入:

 *ID0(<parameters>) 

然后接下来的事情是<参数>返回的函数 。 这个参数是一个简单的字符,所以我们再把它放在一边,因为它非常简单。

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

注意我们添加的圆括号,因为我们再次希望*先绑定, 然后再 (char) 。 否则,它会读取< parameters >返回函数…的函数 。 Noes,甚至不允许函数返回函数。

现在我们只需要把<参数> 。 我将展示一个简短的派生版本,因为我认为你现在已经知道如何去做。

 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中声明的一个很好的属性:声明声明如果这些运算符在使用标识符的expression式中使用,那么它将产生最左边的types。 对于数组也是如此。

希望你喜欢这个小教程! 现在我们可以链接到这个当人们想知道函数的奇怪的声明语法。 我试图尽可能less的C内部。 随意编辑/修复它的东西。

函数指针的另一个很好的用法:
在版本之间切换无痛

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

version.h中

 // First, undefine all macros associated with version.h #undef DEBUG_VERSION #undef RELEASE_VERSION #undef INVALID_VERSION // Define which version we want to use #define DEBUG_VERSION // The current version // #define RELEASE_VERSION // To be uncommented when finished debugging #ifndef __VERSION_H_ /* prevent circular inclusions */ #define __VERSION_H_ /* by using protection macros */ void board_init(); void noprintf(const char *c, ...); // mimic the printf prototype #endif // Mimics the printf function prototype. This is what I'll actually // use to print stuff to the screen void (* zprintf)(const char*, ...); // If debug version, use printf #ifdef DEBUG_VERSION #include <stdio.h> #endif // If both debug and release version, error #ifdef DEBUG_VERSION #ifdef RELEASE_VERSION #define INVALID_VERSION #endif #endif // If neither debug or release version, error #ifndef DEBUG_VERSION #ifndef RELEASE_VERSION #define INVALID_VERSION #endif #endif #ifdef INVALID_VERSION // Won't allow compilation without a valid version define #error "Invalid version definition" #endif 

version.c我将定义version.c的2个函数原型

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

注意函数指针是如何在version.h中原型的

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

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

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

zprintf = &printf; zprintf调用printf进行debugging

要么

zprintf = &noprint; zprintf只是返回,不会运行不必要的代码

运行代码如下所示:

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

如果处于debugging模式,上面的代码将使用printf ,或者在释放模式下不执行任何操作。 这比通过整个项目并注释掉或删除代码要容易得多。 所有我需要做的是在version.h更改版本,代码将完成剩下的工作!

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

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

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

C中函数指针的一个重要用途是调用在运行时select的函数。 例如,C运行库有两个例程qsort和bsearch,它们指向一个被调用的函数来比较两个正在sorting的项目; 这允许您根据您希望使用的任何标准分别对任何内容进行sorting或search。

一个非常基本的例子,如果有一个叫做print(int x,int y)的函数又可能需要调用类似types的add()函数或者sub(),那么我们将会添加一个函数指向print()函数的指针参数如下所示:

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

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

1.首先你需要声明一个指向函数的指针2.通过Desired函数的地址

****注意 – >function应该是相同的types****

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

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

在这里输入图像描述 在32位体系结构中让我们看看机器如何理解上述程序的机器指令。

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

由于函数指针通常是键入的callback函数,因此您可能需要查看types安全的callback函数。 入口点等同于不是callback的函数。

C是相当易变和宽容的同时:)

函数指针在很多情况下都很有用,例如:

  • COM对象成员是指向函数ag的指针: This->lpVtbl->AddRef(This); AddRef是一个指向函数的指针。
  • 函数callback,例如用户定义的函数来比较两个variables作为callback传递给一个特殊的sorting函数。
  • 非常有用的插件实施和应用程序SDK。