나는 최근에 C의 함수 포인터에 대해 약간의 경험이 있습니다.
그래서 여러분 자신의 질문에 답하는 전통을 이어가며, 주제에 대한 빠른 설명이 필요한 사람들을 위해 아주 기본적인 것에 대한 간단한 요약을 하기로 결정했습니다.
질문자 :Community Wiki
나는 최근에 C의 함수 포인터에 대해 약간의 경험이 있습니다.
그래서 여러분 자신의 질문에 답하는 전통을 이어가며, 주제에 대한 빠른 설명이 필요한 사람들을 위해 아주 기본적인 것에 대한 간단한 요약을 하기로 결정했습니다.
의 우리가 가리키는됩니다 기본 기능부터 시작하자 :
int addInt(int n, int m) { return n+m; }
우선,의 2 수신 함수에 대한 포인터를 정의 할 수 int
s와 다시 표시 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 의 상속으로 이동합니다.
ImmutableString
String
의 하위 클래스를 만들고 싶다고 가정해 봅시다. 문자열을 변경할 수 없도록 하려면 get
및 length
set
메서드에 액세스할 수 없으며 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
개체인 base
String.get
및 String.length
메서드를 참조합니다.
함수 포인터를 사용하면 슈퍼클래스에서 메서드를 상속받을 수 있습니다.
우리는 C에서 다형성을 계속할 수 있습니다.
ImmutableString
클래스에서 항상 0
을 반환 length
메서드의 동작을 변경하려는 경우 수행해야 하는 모든 작업은 다음과 같습니다.
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; }
ImmutableString
클래스 length
메서드에 String
클래스와 동일한 동작을 하지 않고 length
lengthOverrideMethod
함수에 정의된 동작을 참조합니다.
C에서 객체 지향 프로그래밍 스타일로 작성하는 방법을 아직 배우고 있다는 면책 조항을 추가해야 합니다. 그래서 아마도 제가 잘 설명하지 못한 요점이 있거나 OOP를 구현하는 가장 좋은 방법의 관점에서 잘못되었을 수 있습니다. 하지만 내 목적은 함수 포인터의 많은 사용 중 하나를 설명하는 것이었습니다.
C에서 객체 지향 프로그래밍을 수행하는 방법에 대한 자세한 내용은 다음 질문을 참조하십시오.
해고 안내: 코드를 직접 컴파일하여 x86 머신의 GCC에서 함수 포인터를 남용하는 방법:
이러한 문자열 리터럴은 32비트 x86 기계어 코드의 바이트입니다. 0xC3
은 x86 ret
명령어 입니다.
일반적으로 손으로 이것을 작성하지 않고 어셈블리 언어로 작성한 다음 nasm
과 같은 어셈블러를 사용하여 C 문자열 리터럴로 16진수 덤프하는 플랫 바이너리로 어셈블합니다.
EAX 레지스터의 현재 값을 반환합니다.
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
스왑 함수 작성
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);
for-loop 카운터를 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
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
에 배치합니다. 이 섹션은 함수 코드와 함께 텍스트 세그먼트의 일부로 연결됩니다.
텍스트 세그먼트에는 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비트 코드에서 작동합니다. 모든 OS의 기본 호출 규칙은 레지스터에서보다 효율적으로 스택에서 인수를 전달합니다. 그러나 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
: 아이디는*D
: D 포인터D(<parameters>)
: <
매개변수 >
반환을 취하는 D 함수 D는 동일한 규칙을 사용하여 작성된 또 다른 선언자입니다. 결국 어딘가에 선언된 엔티티의 이름인 ID
아무 것도 취하지 않고 int를 반환하는 함수에 대한 포인터를 취하고 char를 취하고 int를 반환하는 함수에 대한 포인터를 반환하는 함수를 빌드해 봅시다. 유형 정의를 사용하면 다음과 같습니다.
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);
간단하기 때문에 char 매개변수를 직접 삽입했습니다. D1
을 *D2
로 대체하여 선언자에 대한 포인터를 추가합니다. *D2
괄호로 묶어야 합니다. *-operator
와 함수 호출 연산자 ()
의 우선 순위를 조회하여 알 수 있습니다. 괄호가 없으면 컴파일러는 이를 *(D2(char p))
로 읽습니다. 그러나 그것은 물론 더 이상 *D2
대체하는 것이 아닙니다. 선언자 주위에는 항상 괄호가 허용됩니다. 따라서 실제로 너무 많이 추가해도 잘못되지 않습니다.
(*D2)(char);
반환 유형이 완료되었습니다! D2
<parameters>
반환 하는 함수 선언자 함수로 교체해 보겠습니다. 이는 현재 우리가 있는 D3(<parameters>)
(*D3(<parameters>))(char)
D3
이 이번에는 포인터 선언자가 아니라 함수 선언자가 되기를 원 하기 때문에 괄호가 필요하지 않습니다. 좋습니다. 남은 것은 매개변수뿐입니다. 매개변수는 반환 유형을 수행한 것과 정확히 동일하게 수행되며 char
void
로 대체된 것뿐입니다. 그래서 나는 그것을 복사 할 것입니다 :
(*D3( (*ID1)(void)))(char)
D2
를 ID1
로 대체했습니다. 그 매개변수로 끝났기 때문입니다(이미 함수에 대한 포인터이므로 다른 선언자가 필요하지 않음). ID1
은 매개변수의 이름이 됩니다. 이제 위에서 마지막에 모든 선언자가 수정하는 유형을 추가한다고 말했습니다. 모든 선언의 맨 왼쪽에 나타나는 유형입니다. 함수의 경우 반환 유형이 됩니다. 포인터가 가리키는 유형 등... 흥미로운 점은 유형을 기록할 때 반대 순서로 맨 오른쪽에 나타납니다. :) 어쨌든, 이를 대체하면 완전한 선언이 됩니다. 물론 두 번 모두 int
int (*ID0(int (*ID1)(void)))(char)
그 예에서 ID0
의 식별자를 호출했습니다.
이것은 유형 설명의 맨 왼쪽에 있는 식별자에서 시작하여 오른쪽으로 이동할 때 해당 선언자를 래핑합니다. <
매개변수 >
반환을 취하는 함수로 시작
ID0(<parameters>)
설명의 다음 항목("반환" 후)은 에 대한 포인터 였습니다. 통합해 보겠습니다.
*ID0(<parameters>)
다음은 <
매개변수 >
반환하는 함수였습니다. 매개변수는 간단한 char이므로 매우 간단하므로 바로 다시 넣습니다.
(*ID0(<parameters>))(char)
우리가 추가한 괄호에 주목하세요. 다시 *
먼저 바인딩된 다음 (char)
를 바인드하기를 원하기 때문입니다. <
parameters >
함수를 반환하는 함수를 읽을 것입니다 ... . 아니요, 함수를 반환하는 함수는 허용되지 않습니다.
<
parameters >
만 넣으면 됩니다. 나는 당신이 이미 그것을 수행하는 방법에 대한 아이디어를 이미 가지고 있다고 생각하기 때문에 파생의 짧은 버전을 보여줄 것입니다.
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 내부를 넣으려고 노력했다. 자유롭게 편집/수정할 수 있습니다.
다른 시간에 다른 기능을 원하거나 다른 개발 단계에서 사용할 때 매우 편리합니다. 예를 들어, 콘솔이 있는 호스트 컴퓨터에서 애플리케이션을 개발 중이지만 소프트웨어의 최종 릴리스는 Avnet ZedBoard(디스플레이 및 콘솔용 포트가 있지만 최종 릴리스). 따라서 개발 중에 printf
를 사용하여 상태 및 오류 메시지를 볼 것이지만 작업이 완료되면 아무 것도 인쇄되지 않습니다. 내가 한 일은 다음과 같습니다.
// 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
I는 2 함수를 정의하고있는 본 시제품 version.h
#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를 호출합니다.
또는
zprintf = &noprint
zprintf는 불필요한 코드를 반환하고 실행하지 않습니다.
코드를 실행하면 다음과 같습니다.
#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
정의되며 param 및 반환 값으로 사용됩니다.
위의 답변은 이미 많이 설명되어 있으며 전체 예를 제공합니다.
#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 ∑ } // test - use function pointer as variable, void test_pointer_as_variable() { // create a pointer to function, two_num_operation sum_p = ∑ // 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
int
와 char *
를 받아 int
반환하는 함수입니다.pFunc
취하는 함수의 주소를 할당하는 함수 포인터이다 int
및 char *
및 반환 int
func()
pFunc = func;
에서와 같이 함수 포인터 변수 pFunc
할당되는 소스 라인을 가질 수 있습니다. .
괄호가 자연 연산자 우선 순위 규칙을 극복하는 데 사용되는 함수 포인터 선언/정의와 함께 사용되는 구문에 유의하십시오.
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. }
함수 포인터와 함께 struct
를 사용하는 C 스타일 namespace
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};
그런 다음 전역 구조체 변수의 전체 이름과 함수에 액세스하기 위한 멤버 이름을 지정하여 사용합니다. const
수정자는 실수로 변경되지 않도록 전역에서 사용됩니다.
int abcd = FuncThingsGlobal.func1 (a, b);
함수 포인터의 응용 분야
struct
생성을 지원하는 라이브러리 인터페이스의 팩토리 메서드에서 특정 라이브러리 인터페이스를 요청 namespace
접근 방식과 유사한 작업을 수행할 수 있습니다. 이 라이브러리 인터페이스는 요청된 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의 고전적인 예는 항목 목록을 정렬하거나 정렬된 항목 목록에 대해 이진 검색을 수행하기 위한 조합 순서를 제공하기 위해 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 메시지를 처리하기 위해 유사한 것을 사용합니다.
콜백이 필요한 비동기 함수는 이벤트 핸들러와 유사합니다. 비동기 함수의 사용자는 비동기 함수를 호출하여 일부 작업을 시작하고 작업이 완료되면 비동기 함수가 호출할 함수 포인터를 제공합니다. 이 경우 이벤트는 작업을 완료하는 비동기 함수입니다.
처음부터 시작 기능에는 실행을 시작하는 일부 메모리 주소가 있습니다. 어셈블리 언어에서는 ("함수의 메모리 주소" 호출)로 호출됩니다. 이제 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는 함수의 원하는 주소를 포함합니다.
함수 포인터는 종종 유형이 지정된 콜백이므로 유형 안전 콜백을 살펴보고 싶을 수 있습니다. 콜백이 아닌 함수의 진입점 등에도 동일하게 적용됩니다.
C는 매우 변덕스럽고 동시에 관대합니다. :)
출처 : http:www.stackoverflow.com/questions/840501/how-do-function-pointers-in-c-work
Javascript의 배열에서 빈 요소 제거 (0) | 2023.04.15 |
---|---|
require, include, require_once 및 include_once의 차이점은 무엇입니까? (0) | 2023.04.15 |
고정 헤더를 조정하기 위해 html 앵커 오프셋 [중복] (0) | 2023.04.15 |
jQuery로 확인란이 선택되어 있는지 확인하십시오. (0) | 2023.04.15 |
URL에 대한 Path.Combine? (0) | 2023.04.15 |