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

时间:2009-05-08 15:49:17

标签: c function-pointers

我最近在C中使用了函数指针。

继续回答你自己的问题的传统,我决定对那些需要快速深入研究这些主题的人进行一些基本的总结。

11 个答案:

答案 0 :(得分:1352)

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

答案 1 :(得分:281)

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

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

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方法将无法访问,同时保持对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类中一直更改0方法的行为以返回ImmutableString,那么所有必须做的就是到:

  1. 添加一个将用作覆盖length方法的函数。
  2. 转到“构造函数”并将函数指针设置为覆盖length方法。
  3. length中添加覆盖ImmutableString方法可以通过添加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类的行为。 {{1}}函数。

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

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

答案 2 :(得分:212)

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

这些字符串文字是32位x86机器代码的字节。 0xC3an x86 ret instruction

你通常不会手工编写这些,你用汇编语言编写,然后使用像nasm这样的汇编程序将它组装成一个平面二进制文件,然后将其转换为C字符串文字。

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

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. 编写交换功能

    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. 您甚至可以编写一个计数为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);
    
  5. 请注意,编译器将字符串文字放在.rodata部分(或Windows上的.rdata)中,该部分作为文本段的一部分链接(以及函数代码)。

    文本段具有Read + Exec权限,因此将字符串文字转换为函数指针可以在不需要mprotect()VirtualProtect()系统调用的情况下运行,就像您需要动态分配的内存一样。 (或gcc -z execstack将程序与堆栈+数据段+堆可执行文件链接起来,作为快速入侵。)


    要反汇编这些,您可以编译它以在字节上放置标签,并使用反汇编程序。

    // 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";
    

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

    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
    

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

答案 3 :(得分:103)

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

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

答案 4 :(得分:23)

答案 5 :(得分:22)

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

当您在不同时间或不同开发阶段需要不同功能时,它们非常便于使用。例如,我正在一台带有控制台的主机上开发应用程序,但该软件的最终版本将放在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.h

中的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()函数,zprintfversion.h分配了一个唯一函数(其函数签名匹配),具体取决于zprintf = &printf; <中定义的版本/ p>

zprintf = &noprint; zprintf调用printf进行调试

#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; } zprintf只返回并且不会运行不必要的代码

运行代码如下所示:

mainProg.c

printf

如果处于调试模式,上面的代码将使用version.h,如果处于发布模式,则不执行任何操作。这比通过整个项目并注释掉或删除代码要容易得多。我需要做的就是更改{{1}}中的版本,代码将完成其余的工作!

答案 6 :(得分:13)

函数指针通常由typedef定义,并用作param&amp;返回值,

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

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

答案 7 :(得分:12)

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

一个非常基本的例子,如果有一个名为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

答案 8 :(得分:4)

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

我能想到的唯一例外是将函数指针视为指向单个值以外的其他东西。通过递增或递减函数指针或对函数指针增加或减去偏移量来进行指针算术并不是真正的实用程序,因为函数指针仅指向单个对象,即函数的入口点。

函数指针变量的大小,该变量占用的字节数可能会根据基础架构而有所不同,例如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是一个函数,它接受一个int和一个char *并返回一个int
  • pFunc是一个函数指针,为该函数指针分配了一个函数地址,该函数采用一个int和一个char *并返回一个int

因此从上面我们可以看到一个源代码行,其中的函数func()的地址被分配给函数指针变量pFunc,就像pFunc = func;一样。

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

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

几个不同的用法示例

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

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

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

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 nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

C样式转换

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

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.

比较功能指针是否相等

您可以使用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 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.
}

C样式namespace与函数指针一起使用全局struct

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

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

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;

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

然后将通过指定全局struct变量的完整名称和成员名称来使用它来访问该函数。 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消息。

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

答案 9 :(得分:3)

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

1.首先,您需要声明一个指向函数的指针 2.通过所需功能的地址

****注意 - &gt;功能应该是相同类型****

这个简单的程序将说明每一件事。

#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 here之后让我们看看机器是如何理解它的。在32位架构中了解上述程序的机器指令。

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

答案 10 :(得分:0)

由于函数指针通常是键入的回调函数,因此您可能需要查看type safe callbacks。这同样适用于非回调函数的入口点等。

C在同一时间非常善变和宽容:)