programing

C의 함수 포인터는 어떻게 작동합니까?

prostudy 2022. 6. 25. 20:25
반응형

C의 함수 포인터는 어떻게 작동합니까?

최근에 C에서 함수 포인터를 사용한 경험이 있습니다.

그래서 여러분 자신의 질문에 답하는 전통에 따라, 저는 그 주제에 대한 빠른 조사가 필요한 분들을 위해 아주 기본적인 사항들을 간단히 요약하기로 했습니다.

C의 함수 포인터

우선, 다음과 같은 기본적인 기능부터 설명하겠습니다.

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

2 아, 2 아, 2 아, 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 operator의 것 .String을 이루다"hello".

함수 포인터를 사용함으로써 C의 메서드를 에뮬레이트할있다.

어떻게 하면 될까요?

String는 "class"입니다.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;
}

있기 호출할 object"가internal되어 있을 입니다.struct이것은 이전 코드 리스트에서 생략되었습니다.이것은 정보를 숨기는 방법이지만, 함수 포인터와는 관련이 없습니다).

할 수 보다 할 수 있는 게 더 많아요.s1->set("hello"); , , , on on , , , , , , 。s1->set(s1, "hello")

이 사소한 설명은 자신을 언급하는 것으로부터 벗어나야 하므로, 다음 부분인 C의 상속으로 넘어가겠습니다.

, '아부 '를 String a, say aImmutableString.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된 코드는 " "commonor" 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 ""를 합니다.String.get ★★★★★★★★★★★★★★★★★」String.length "Manages"를 base으로 저장된 String★★★★★★ 。

함수 포인터를 사용하면 슈퍼클래스에서 메서드를 상속할 수 있습니다.

우리는 C에서 다형성을 계속할 수 있다.

, ' 변경'을, '동작 변경'을 하고 싶은 경우,length 0ImmutableString어떤 이유로든, 해야 할 일은 다음과 같습니다.

  1. 합니다.length★★★★★★ 。
  2. 로 이동하여 함수 하는 '컨스트럭터'로 합니다.length★★★★★★ 。

" " " "length in the method in the method in 。ImmutableString를 더하면 됩니다.lengthOverrideMethod:

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

다음에 '다음에', '다음에', '다음에', '다음에', '다음에'의 기능 포인터.length의 메서드가 'CHANGE'에 .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 in the method in the method in 。ImmutableStringclass class class 。String는 '''), '''로 변경)length는 method에 되어 있는 을 참조합니다.lengthOverrideMethod★★★★★★ 。

C에서 객체 지향 프로그래밍 스타일을 사용하여 쓰는 방법을 아직 배우고 있다는 면책사항을 추가해야 합니다.따라서 제가 설명을 잘 못 했거나 C에서 OOP를 가장 잘 구현하는 방법에 대해 잘못 알고 있을 수도 있습니다.하지만 제 목적은 함수 포인터의 많은 사용법 중 하나를 설명하는 것이었습니다.

C에서 객체 지향 프로그래밍을 실행하는 방법에 대한 자세한 내용은 다음 질문을 참조하십시오.

해고 가이드:코드를 손으로 컴파일하여 x86 머신에서 GCC의 함수 포인터를 사용하는 방법:

이러한 문자열 리터럴은 32비트 x86 머신 코드의 바이트입니다. 0xC3는 x86 명령입니다.

nasmC 문자열 리터럴로 16진수 덤프하여 플랫 바이너리로 만듭니다.

  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. 1000에 for-loop 카운터를 쓰고, 매번 함수를 호출합니다.

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

을 에 해 주세요..rodata 션또또또.rdata(Windows의 경우) 텍스트 세그먼트의 일부로 링크되어 있습니다(함수 코드와 함께).

는 ReadManagement+Management+Management+Manager는 Read+Manager로 되어 있습니다.exec으로 스트링 하면 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에 위반되어 일반적으로 비효율적이라는 것을 알 수 있습니다.

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비트 코드로 동작합니다.이러한 모든 OS의 디폴트 호출 규약은 레지스터가 아닌 스택 상에서 arg를 전달합니다.arg를 전달합니다.단, EBX는 모든 통상의 콜링 규칙에서 콜프리저브되어 있기 때문에 저장/복원하지 않고 스크래치 레지스터로 사용하면 발신자가 크래쉬하기 쉽습니다.

내가 가장 좋아하는 기능 포인터 사용법 중 하나는 값싸고 쉬운 반복기입니다.

#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의 규칙에 따라

1. 먼저 함수 2에 대한 포인터를 선언해야 합니다.원하는 함수의 주소를 전달합니다.

****주의-> 함수는 같은 타입이어야 합니다****

이 간단한 프로그램은 모든 것을 설명할 것입니다.

#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에는 함수의 원하는 주소가 포함됩니다.

함수 포인터는 함수의 주소를 포함하는 변수입니다.일부 제한된 속성을 가진 포인터 변수이므로 데이터 구조의 다른 포인터 변수와 거의 동일하게 사용할 수 있습니다.

생각할 수 있는 유일한 예외는 함수 포인터를 단일 값이 아닌 다른 값을 가리키는 것으로 취급하는 것입니다.함수 포인터를 증가 또는 감소시키거나 함수 포인터에 오프셋을 추가/축소함으로써 포인터 계산을 수행하는 것은 함수 포인터가 함수의 진입점인 단일 사물만을 가리키기 때문에 실제로 유용하지 않습니다.

함수 포인터 변수의 크기, 변수가 차지하는 바이트 수는 기본 아키텍처(예: 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 a. a. a.char * and and and및다 an 。int
  • pFunc는 함수 이며, .int a. a. a.char * and and and및다 an 。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.

함수 포인터와 동일성 비교

Pointer를 사용하면 Function Address를 기능 한지 확인할 수 있습니다.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컴파일러에서 될 수 뿐) 다음과 .이는 함수에 대한 함수 포인터 파라미터에도 적용됩니다.

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 pointers를

.static: 하고 이를 에 할당합니다.은, 「 스코프」와 을 제공하는 방법입니다.namespaceC++면 됩니다.

헤더 파일에서 사용하는 글로벌 변수와 함께 네임스페이스가 될 구조를 정의합니다.

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

그런 다음 글로벌 구조 변수의 완전한 이름과 함수에 액세스하기 위한 멤버 이름을 지정함으로써 사용됩니다.const수식자는 실수로 변경할 수 없도록 글로벌하게 사용됩니다.

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

기능 포인터의 응용 프로그램 영역

는 C DLL과 을 할 수 .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 소스 코드를 사용하는 경우입니다.GUI 소스 코드에서는 특정 이벤트의 핸들러가 이벤트가 발생했을 때 실제로 호출되는 함수 포인터를 제공하여 등록됩니다.메시지 맵을 사용하는 Microsoft MFC 프레임워크에서는 윈도 또는 스레드에 전달되는 Windows 메시지 처리에 유사한 기능이 사용됩니다.

콜백을 필요로 하는 비동기 함수는 이벤트핸들러와 비슷합니다비동기 함수 사용자는 비동기 함수를 호출하여 액션을 시작하고 액션이 완료되면 비동기 함수가 호출하는 함수 포인터를 제공합니다.이 경우 이벤트는 작업을 완료하는 비동기 함수입니다.

C의 함수 포인터의 큰 용도 중 하나는 런타임에 선택된 함수를 호출하는 것입니다.예를 들어, C 런타임라이브러리에는 2개의 루틴과 및 이 루틴은 정렬되어 있는2개의 항목을 비교하기 위해 호출되는 함수에 포인터를 가져갑니다.이것에 의해, 사용하는 조건에 근거해, 임의의 것을 각각 정렬 또는 검색할 수 있습니다.

로는 '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;
}

출력은 다음과 같습니다.

입니다.
: 390파운드

포인터는 '아까보다'로 됩니다.typedef&value로

위의 답변은 이미 많은 것을 설명하고 있으며, 예를 들면 다음과 같습니다.

#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는 꽤 변덕스러우면서도 관대하다:)

함수 포인터는 기본 선언자가 있으면 선언하기 쉬워집니다.

  • id:ID: ID는 a입니다.
  • 인터::*D: D 포인터
  • 능::D(<parameters>): 파라미터를 반환하는> D 함수

한편, D는 이러한 규칙을 사용하여 작성된 또 다른 선언자입니다.어디선가은...ID(아래 예 참조)는 선언된 엔티티의 이름입니다.아무것도 받지 않고 int를 반환하는 함수에 포인터를 가져가고, char를 반환하고 int를 반환하는 함수에 포인터를 반환하는 함수를 구축해 보겠습니다.는 식입니다.

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

보시다시피, typedef를 사용해서 만드는 것은 꽤 쉽습니다.없으면규칙을 일관되게에도 어렵지 .typedefs는 입니다.보시다시피 포인터가 가리키는 부분과 함수가 반환하는 것을 놓쳤습니다.이는 선언문 맨 왼쪽에 나타나며 관심사는 아닙니다.이미 선언자를 작성했다면 마지막에 추가됩니다.그렇게 합시다., 첫 - 관,, - 다 using using using using building building building building building building building building building building 。[ ★★★★★★★★★★★★★★★★★」]:

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

보시는 바와 같이 선언자를 하나씩 추가함으로써 유형을 완전히 기술할 수 있습니다.공사는 두 가지 방법으로 할 수 있다.하나는 바로 올바른 것(잎)부터 시작하여 식별자까지 작업하는 상향식입니다.또 다른 방법은 식별자에서 시작하여 잎사귀까지 작업하는 하향식입니다.양쪽 다 보여드릴게요.

보텀 업

건설은 오른쪽에 있는 것부터 시작합니다.반환된 것은 char를 취하는 함수입니다.선언자를 구별하기 위해 번호를 매길게요

D1(char);

trivialchar가 했습니다.그것은 사소한 것이기 때문입니다.「」를 치환해, 한다.D1타타에 *D2 괄호를 *D2. 의알 수 *-operator operator " " and 。()*(D2(char p))을 D1로 *D2선언자할 수 .선언자 주변에는 항상 괄호를 사용할 수 있습니다.그래서 사실 너무 많이 넣어도 틀리지 않아요.

(*D2)(char);

품!타!!!!!!!이제 대체하겠습니다.D2반환을 취하는 함수 선언자 함수에 의해, 즉D3(<parameters>)지금 우리가 있는 그대로야

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

괄호는 필요없다는 점에 유의하십시오. D3이번에는 포인터 선언자가 아니라 함수 선언자가 될 것입니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 반환유형은 반환유형입니다.charreplaced replaced void복사해 보겠습니다.

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

체교다다 i를 했다.D2타타에 ID1이 파라미터는 종료되었으므로(이미 함수에 대한 포인터이므로 다른 선언자가 필요 없습니다). ID1파라미터의 이름이 됩니다.위의 마지막에는 모든 선언자가 수정하는 타입이 추가되어 있습니다.즉, 모든 선언의 맨 왼쪽에 나타나는 타입입니다.을 사용하다「 」「 」「 」「 」「 」「 。활자를 적으면 오른쪽 끝에 반대 순서로 나타납니다.어쨌든, 치환하면 완전한 선언이 됩니다. 번 다 하다int물론이야.

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

ID0를 참조해 주세요.

톱 다운

이것은, 타입의 설명의 왼쪽 끝에 있는 식별자로부터 개시해, 오른쪽을 통과할 때에 선언자를 감싼다.함수를 사용하여 매개> 변수를 반환하는 것부터 시작합니다.

ID0(<parameters>)

설명의 다음('돌아가기' 후)은 포인터였습니다.통합합시다.

*ID0(<parameters>)

그리고 나서 그 다음은 파라미터를 되돌리는> 기능이었다.파라미터는 단순한 문자이기 때문에 매우 사소한 것이기 때문에 즉시 다시 입력합니다.

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

추가에 주의해 한 번 '''가 입니다.시시시시시 시시시시시*먼저 바인드하고 다음으로(char). 그렇지 않으면 함수를 읽을 수 있으며 반환되는 함수를 파라미터로 사용합니다>.아니요, 함수를 되돌리는 기능은 허용되지 않습니다.

, 그럼 ㄹ게요 라고 하면 요.<>전달의 간단한 버전을 보여 드리겠습니다.여러분은 이미 방법을 알고 있을 겁니다.

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 내장을 적게 넣으려고 했다.자유롭게 편집/수정할 수 있습니다.

수수른른 、 [ 。
간 은 간단하다

다른 시기 또는 다른 개발 단계에서 다른 기능을 원할 때 매우 편리합니다.예를 들어, 나는 콘솔이 있는 호스트 컴퓨터에 공감하고 있지만 소프트웨어의 최종 해제한 애브넷 ZedBoard(지만, 그들은 최종 석방을 위해 needed/wanted지 않디스플레이와 콘솔용 포트가 있)에 사용하게 할 것 애플리케이션을 개발합니다.예를 들어, 콘솔이 있는 호스트 컴퓨터에서 애플리케이션을 개발하고 있지만, 소프트웨어의 최종 릴리스는 애브넷 ZedBoard(디스플레이 및 콘솔용 포트가 있지만 최종 릴리스에는 필요/원하지 않음)에 배치됩니다.그래서 개발하는 동안, 나는그래서 개발하는 동안을 사용할 것이다.printf지만, 내가 다 창조했을 때, 나는 인쇄된 것을 원하지 않아 상태 및 오류 메시지를 보려면.상태 및 오류 메시지를 볼 수 있지만 작업이 끝나면 인쇄를 원하지 않습니다.저는 이렇게 했습니다:제가 한 일은다음과 같습니다.

version.

// 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나는 prototypes 2가지기능 프로토타입을 정의하겠습니다에 현재 2기능'을 정의할 것이다.version.h

버전.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()어디 기능하다 기능zprintf(는 기능을 가진 서명과 일치하는)는 에에 정의되어 있는 버전에 따라 고유한 기능(일치하는 기능 시그니처)이 할당됩니다가 정의된 버전에 따라서 독특한 기능 할당됩니다.version.h

zprintf = &printf;Zprintf purposeszprintf는 디버깅을위해 printf를 호출합니다 디버깅에 printf 요구하고 있다.

또는

zprintf = &noprint;Zprintf 그냥 반품과 불필요한 codezprintf는 반환만하지 않을 수 할 뿐 불필요한 코드는 실행되지 않습니다.

코드를 실행하면 다음과 같이 됩니다.

메인 프로그램

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

위의 코드위의 코드는을 사용할 것이다.printf디버깅 모드일 경우 또는 릴리스 모드일 경우 아무것도 하지 않습니다.이 방법은 프로젝트 전체를 검토하여 코드에 주석을 달거나 코드를 삭제하는 것보다 훨씬 쉽습니다. 할 은 '을 바꾸기만 하면 .version.h머지지암암!

언급URL : https://stackoverflow.com/questions/840501/how-do-function-pointers-in-c-work

반응형