C中的函数指针如何工作?
我最近在C中使用了函数指针。
因此,继续回答您自己的问题的传统,我决定对那些需要快速介入该主题的人做一个基本的小结。
C中的函数指针
我们先来看一个我们将要指出的基本功能:
int addInt(int n, int m) {
return n+m;
}
首先,让我们定义一个指向一个函数的指针,它接收2个int
并返回一个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 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");
是的, ->
和缺少一个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")
执行操作。
有了这个小小的解释,我们就不得不将自己的引用传递给自己,我们将转向下一部分,这是C中的继承 。
假设我们想要创建一个String
的子类,比如一个ImmutableString
。 为了使字符串不可变, set
方法将不可访问,同时保持对get
和length
访问,并强制“构造函数”接受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
, get
和length
方法的函数指针通过遍历base
变量(这是一个内部存储的String
对象)实际引用String.get
和String.length
方法。
函数指针的使用可以实现父类中方法的继承。
我们可以继续在C中继承多态 。
例如,如果我们想要改变length
方法的行为,以便由于某种原因在ImmutableString
类中始终返回0
,所有必须完成的操作是:
length
方法的函数。 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
在方法ImmutableString
类作为String
类,现在length
方法将是指在定义的行为lengthOverrideMethod
功能。
我必须添加一个免责声明,我仍然在学习如何用C编写一个面向对象的编程风格,所以可能有些问题我没有解释清楚,或者可能只是关于如何最好地实现面向对象编程在C中,但我的目的是试图说明函数指针的许多用途之一。
有关如何在C中执行面向对象编程的更多信息,请参阅以下问题:
被引发的指南:如何通过手动编译代码来滥用x86机器上的GCC中的函数指针:
返回EAX寄存器的当前值
int eax = ((int(*)())("xc3 <- This returns the value of the EAX register"))();
写一个交换功能
int a = 10, b = 20;
((void(*)(int*,int*))"x8bx44x24x04x8bx5cx24x08x8bx00x8bx1bx31xc3x31xd8x31xc3x8bx4cx24x04x89x01x8bx4cx24x08x89x19xc3 <- This swaps the values of a and b")(&a,&b);
写一个for循环计数器到1000,每次调用一些函数
((int(*)())"x66x31xc0x8bx5cx24x04x66x40x50xffxd3x58x66x3dxe8x03x75xf4xc3")(&function); // calls function with 1->1000
你甚至可以编写一个计数为100的递归函数
const char* lol = "x8bx5cx24x4x3dxe8x3x0x0x7ex2x31xc0x83xf8x64x7dx6x40x53xffxd3x5bxc3xc3 <- Recursively calls the function at address lol.";
i = ((int(*)())(lol))(lol);