문제

나는 일부 경험을 최근 함수 포인터와 C.

그래서에의 전통은,자신의 질문에 대한 답변을 만들기로 결정했 작은 요약을 매우 기초를 필요로하는 사람들을 위해,빠른 다이빙에서 주제입니다.

도움이 되었습니까?

해결책

c.의 함수 포인터

우리가 될 기본 기능부터 시작합시다 지적:

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

첫째, 2를 수신하는 함수에 대한 포인터를 정의하겠습니다. ints와 반환 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");

예, -> 그리고 a의 부족 new 운영자는 죽은 사람이지만, 우리가 일부의 텍스트를 설정한다는 것을 암시하는 것 같습니다. String 수업이 될 수 있습니다 "hello".

기능 포인터를 사용하여 c에서 방법을 모방 할 수있다.

이것은 어떻게 성취됩니까?

그만큼 String 수업은 실제로 a입니다 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 액세스를 유지하면서 방법은 액세스 할 수 없습니다. 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 방법은 실제로 String.get 그리고 String.length 방법을 통해 방법 base 내부적으로 저장된 변수 String 물체.

함수 포인터를 사용하면 슈퍼 클래스에서 방법의 상속을 달성 할 수 있습니다.

우리는 계속해서 계속할 수 있습니다 c.의 다형성.

예를 들어, 우리가의 동작을 바꾸고 싶다면 length 반환 방법 0 항상 ImmutableString 어떤 이유로 든 수업에서해야 할 일은 다음과 같습니다.

  1. 재정의 역할을 할 함수 추가 length 방법.
  2. "생성자"로 이동하여 기능 포인터를 재정의로 설정하십시오. length 방법.

재정의 추가 length 방법 ImmutableString an을 추가하여 수행 할 수 있습니다 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의 기능 포인터를 남용하는 방법 손으로 코드를 컴파일하여 :

이 문자열 리터럴은 32 비트 x86 기계 코드의 바이트입니다. 0xC3 ~이다 x86 ret 지침.

당신은 일반적으로 이것을 손으로 쓰지 않을 것입니다. 어셈블리 언어로 작성한 다음 어셈블러를 사용합니다. nasm CR 스트링 리터럴에 헥스 럼프로 플랫 바이너리로 조립합니다.

  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에서) (함수 코드와 함께).

텍스트 세그먼트에는 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를 위반하고 일반적으로 비효율적이라는 것을 알 수 있습니다.

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 비트 코드로 작동합니다. 모든 OSE의 기본 호출 규칙은 레지스터에서보다 효율적으로 스택에 args를 전달합니다. 그러나 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);
}

함수 포인터가 쉽게 선언하면 당신은 기본적인 선언자:

  • id: ID: ID
  • 포인: *D: D 포인터
  • 기능: D(<parameters>): D 기능 <매개변수> 로 돌아

는 동안 또 다른 선언자를 사용하여 구축 그 동일한 규칙이 있습니다.결국,어딘가에,그것으로 끝나 ID (아래 예시를 참고),이는 이름의 신 entity.자를 구축하려고에 사용 함수 포인터 기능을 아무것도 아무도 돌아 int,그리고 포인터를 반환하는 기능을 복용하는 문자 및 반환 int.와 type-defs 그것은 다음과 같이

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

삽입된 문자 매개 변수를 직접 이후,그것은 간단하다.추가에 대한 포인터 선언자에 의해 대체 D1 by *D2.참고는 우리가 포장 괄호 *D2.할 수 있는 알려진에 의해 보고의 우선 순위 *-operator 고 함수 호출 연산자 ().없이 우리의 괄호는 컴파일러가 그것을 읽으로 *(D2(char p)).그러나지 않는 것은 일반의 대체 D1 의 *D2 더 이상은,물론입니다.괄호는 항상 허용되는 약 선언자.그래서 당신은하지 않는게 아무것도 잘못된 경우 추가 너무 많은 그들의합니다.

(*D2)(char);

환 타입이 완료됩니다!지금하자,대체 D2 함수에 의해 선언 기능 <parameters> 로 돌아, 는 D3(<parameters>) 우리는 지금이다.

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

참고 괄호가 필요한 이후,우리는 고 싶 D3 하는 함수로 선언하고 아닌 포인터 선언자 이 시간입니다.큰 것으로,남아있는 유일한 방법은 매개변수에 대한니다.이 매개 변수는 똑같은 수행으로 우리가 수행 반환 입력,단 char 에 의해 대체 void.그래서 나는 그것을 복사:

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

나는 대체 D2 by ID1, 이후,우리는 우리와 함께 완료하는 매개변수(이것은 이미 포인터 기능이 필요 없는 또 다른 선언 자). ID1 될 것입의 매개 변수의 이름입니다.지금,나는 위에서 말한 끝에 하나의 추가 입력되는 모든 사람 선언자 수정을 하에서 나타나는 매우 왼쪽의 모든 선언입니다.를 위한 함수가 되는 반 형식입니다.에 대한 포인터 지적하는 형식 등등...그것은 흥미로운 경우 서면 아래 유형,그것이 나타납니다 반대기에 오른쪽)어쨌든,대체 그것은 수익률을 완료 선언입니다.모 int 물론입니다.

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

나라의 식별자는 기능 ID0 에는 예입니다.

상위 아래로

이에서 시작한 식별자에서 매우 왼쪽에 설명의 유형,포장하는 선언자로서 우리는 우리의 방법을 통해 오른쪽에 있습니다.시작 기능 <매개변수> 로 돌아

ID0(<parameters>)

다음에 대한 설명(후"돌아온")였 포인터.자의 통합:

*ID0(<parameters>)

그 다음 일 functon 그 <매개변수> 로 돌아.매개 변수가 간단한 문자,그래서 우리는 그것은 오른쪽에 떨어져 다시 때문에 그것은 정말 하찮다.

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

참고 괄호 안의 우리가 추가되기 때문에 우리는 다시 원하는 * 바인딩은 첫째,고 다음(char).그렇지 않으면 그것을 읽 기능 <매개변수> 반환 기능을....개인 수영장,야외 수영,반환하는 함수 기능이 없더라도 허용합니다.

이제 우리는 그냥 넣어야 <매개변수>.나의 짧은 버전 deriveration 때문에,나는 당신이 이미 이해서 지금 생각 어떻게 그것을 할 수 있습니다.

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

그냥 넣 int 하기 전에 선언자 같은 우리와 함께 하단까지,그리고 우리는 완제

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

좋은 일

는 bottom-up or top-down 더 나은?나는 사용을 하지만,어떤 사람들 수 있습니다 더 편안한 최고입니다.그것은 문제의 맛이 나는 생각한다.덧붙여,적용할 경우 모든 사업자는 선언을,당신은 끝날 것이 점점 int:

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

는 객실 tv 에서 위성 채널을 시청하실 수의 선언에서 C:선언이 주장하는 경우에는 그 사업자가에 사용되는 표현을 사용하여 식별자,그 수익률이 유형에습니다.그것은 좋아하는 배열에 대해 너무입니다.

희망은 당신이 좋아하는 이 작은 튜토리얼!이제 우리가 할 수 있습에 대한 링크를 이 때 사람들은 궁금해에 대해 이 선언문의 기능입니다.나를 넣어로 작은 C internals 으로 가능합니다.료를 수정/일을 해결에 있습니다.

기능 포인터에 대한 또 다른 용도 :
버전 사이를 통증없이 전환합니다

그들은 다른 시간에 다른 기능을 원하거나 개발의 다른 단계를 원할 때 사용하기에 매우 편리합니다. 예를 들어, 콘솔이있는 호스트 컴퓨터에서 애플리케이션을 개발하고 있지만 소프트웨어의 최종 릴리스는 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 존재하는 2 개의 기능 프로토 타입을 정의 할 것입니다 version.h

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()어디에서 기능합니다 zprintf 정의 된 버전에 따라 고유 함수 (기능 서명 일치)가 할당됩니다. version.h

zprintf = &printf; zprintf는 디버깅 목적으로 printf를 호출합니다

또는

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

위의 코드는 사용됩니다 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의 함수 포인터에 대한 큰 용도 중 하나는 런 타임에서 선택한 함수를 호출하는 것입니다. 예를 들어, C 런타임 라이브러리에는 두 가지 루틴이 있습니다. qsort 그리고 bsearch, 이는 정렬중인 두 항목을 비교하기 위해 호출되는 함수에 대한 포인터를 가져옵니다. 이를 통해 사용하려는 기준에 따라 각각 정렬하거나 검색 할 수 있습니다.

매우 기본적인 예, 하나의 기능이있는 경우 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 an을 취하는 함수입니다 int 그리고 a char * 그리고 반환합니다 int
  • pFunc 기능 포인터는 int 그리고 a 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 컴파일러는 오류가 아닌 수표에 대해 LAX 일 수 있거나 경고를 제공 할 수 있습니다.

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 이름이 파일 스코프 인 함수를 지정한 다음과 유사한 것을 제공하는 방법으로 글로벌 변수에이를 지정하는 키워드 namespace C ++의 기능.

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

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

기능 포인터의 응용 영역

DLL 라이브러리 구성 요소는 C 스타일과 비슷한 일을 할 수 있습니다. namespace 특정 라이브러리 인터페이스가 A의 생성을 지원하는 라이브러리 인터페이스의 공장 메소드에서 요청되는 접근 방식 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() 항목 목록을 정렬하거나 정렬 된 항목 목록을 통해 이진 검색을 수행하기위한 Collation Order를 제공합니다. 비교 함수는 정렬 또는 이진 검색에 사용 된 Collation 알고리즘을 지정합니다.

다른 용도는 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 또는 스레드로 전달되는 Windows 메시지를 처리합니다.

콜백이 필요한 비동기 기능은 이벤트 핸들러와 유사합니다. 비동기 함수의 사용자는 비동기 기능을 호출하여 어떤 동작을 시작하고 동작이 완료되면 비동기 기능이 호출되는 함수 포인터를 제공합니다. 이 경우 이벤트는 작업을 완료하는 비동기 기능입니다.

스크래치에서 시작하여 실행을 시작하는 메모리 주소가 있습니다. 어셈블리 언어로서 ( "함수의 메모리 주소"호출) 이제 C로 돌아 오면 기능이 메모리 주소가 있으면 C의 포인터로 조작 할 수 있습니다.

1. 기능으로 포인터를 선언해야합니다.

**** 참고-> 함수는 동일한 유형이어야합니다 ****

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

#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그 후 머신이 어떻게 이해하는지 살펴 보겠습니다.

Red Mark 지역은 주소가 어떻게 교환되고 EAX에 저장되는지 보여줍니다. 그런 다음 그들은 EAX의 통화 명령입니다. EAX에는 기능의 원하는 주소가 포함되어 있습니다.

기능 포인터는 종종 입력 된 콜백이므로 살펴 보는 것을 원할 수 있습니다. 안전한 콜백을 입력하십시오. 콜백이 아닌 함수의 진입 지점 등에 동일하게 적용됩니다.

C는 매우 변덕스럽고 동시에 용서합니다 :)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top