함수를 반환하는 함수를 만들려면 어떻게 해야 합니까?
전체 그림:저는 기능이 있는 모듈과 그 기능에 대한 절차와 기능이 있는 모듈을 가지고 있습니다.
두 가지 기능을 결합할 때(기능의 모듈 인터페이스에서):
double f1(double alpha, double x);
double f2(double beta, double x);
여러 가지 방법으로, (그 중 하나는 추가하는 것입니다):
double OP_Addition(double (*f)(double,double) , double (*g)(double,double), double param1, double param2, double x);
다음(일부) 구현에 문제가 없습니다.
z1 = (*f)(param1, x);
z2 = (*g)(param2, x);
y = z1 + z2;
return y;
그러나 포인터를 "새" 함수로 되돌리고 싶을 때는 다음과 같습니다.
void *OP_PAdd( double (*f)(double,double), double param3 );
제대로 작동하지도 않고 "통화"도 제대로 할 수 없습니다.출력 "함수"를 다른 함수의 입력으로 사용하고 싶습니다.
함수에서 할 때 은 다른함서함반수환때할방은법깨가끗한장를에수▁a▁when▁with방은입니다.typedef:
typedef double (*ftype)(double, double);
그런 다음 다음과 같이 기능을 선언할 수 있습니다.
ftype OP_PAdd( ftype f, double param3 )
{
....
return f1;
}
당신은 이것을 할 수 있습니다.typedef하지만 지저분합니다.
double (*OP_PAdd( double (*f)(double,double), double param3 ))(double,double)
{
return f1;
}
따라서 함수 포인터가 다른 함수의 매개 변수 또는 반환 값으로 있을 때는typedef.
편집:
다음과 같이 유형을 선언할 수 있습니다.
typedef double ftype(double, double);
실제로는 이런 종류를 직접 사용할 수 없습니다.함수는 함수를 반환할 수 없으며(함수에 대한 포인터만 반환함), 이 유형의 변수는 할당할 수 없습니다.
또한 함수를 호출하기 위해 함수 포인터를 명시적으로 참조 해제할 필요가 없으므로 포인터 자체가 숨겨져 있다는 사실은 큰 문제가 되지 않습니다.를 수포인다같음정이의것관하다니례도입는과로 정의하는 typedef다음에 대한 관리 페이지:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
다른 답들은 올바르고 흥미롭지만, 휴대용 C99에서는 C 기능으로 진정한 폐쇄를 할 수 있는 방법이 없다는 것을 알아야 합니다(그리고 이것은 C의 근본적인 한계입니다).폐쇄가 무엇인지 모르는 경우, 폐쇄에 대한 위키 페이지를 주의 깊게 읽으십시오(그리고 SICP, 특히 § 1.3도 읽으십시오).그러나 C++11에서는 std:: 함수 및 람다-식을 사용하는 폐쇄가 있습니다.그리고 대부분의 다른 프로그래밍 언어(Ocaml, Haskell, Javascript, Lisp, Clojure, Python 등)에는 폐쇄가 있습니다.
C에서 진정한 폐쇄가 없기 때문에(수학적으로 C 함수의 유일한 닫힌 값은 전역 또는 정적 변수 또는 리터럴), C 함수 포인터를 허용하는 대부분의 라이브러리는 일부 클라이언트 데이터(간단한 예는 qsort_r일 수 있지만 GTK 내부를 더 진지하게 살펴봄) API 처리 콜백을 제공합니다.해당 클라이언트 데이터(일반적으로 불투명 포인터)는 닫힌 값을 유지하는 데 사용될 수 있습니다.당신은 아마도 유사한 규약을 따르고 싶을 것입니다(일부 추가 클라이언트 데이터와 함께 함수 포인터를 콜백으로 체계적으로 전달). 따라서 C 함수의 서명을 변경해야 할 것입니다(원시 함수 포인터만 전달하는 대신 함수 포인터와 일부 클라이언트 데이터를 콜백으로 전달하여 폐쇄를 "에뮬레이트"합니다).
때때로 운영 체제 또는 일부 외부 라이브러리의 도움을 받아 비표준 기능을 사용하여 런타임에 C 함수를 생성할 수 있습니다.당신은 J를 좀 사용할 수 있습니다.GNU lightning, libjit(둘 다 일부 느린 실행 코드를 빠르게 생성함), asmjit(각 기계 명령을 명시적으로 생성함), GCCJIT 또는 LLVM(둘 다 기존 컴파일러 위에 있으므로 약간 천천히 최적화된 코드를 생성하는 데 사용할 수 있음)과 같은 IT 컴파일 라이브러리입니다.POSIX & Linux 시스템에서는 일부 임시 파일에서 일부 C 코드를 내보낼 수도 있습니다./tmp/tempcode.c 컴을포니예다합크일파예(▁a:)gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so그 코드의 )을 플러그인에 넣고 dlopen(3) & dlsym(3)을 사용하여 생성된 플러그인을 동적으로 로드합니다.
그나저나 우리는 당신이 코딩하고 있는 실제 응용 프로그램이 무엇인지 모르지만, 당신은 루아나 길레와 같은 통역기를 그 안에 포함시키는 것을 고려할 수 있습니다.그런 다음 를 사용하여 내장된 평가자/통역자에게 콜백을 제공합니다.
이와 같은 것을 말씀하시는 건가요?decider()function은 포인터를 다른 함수로 반환한 다음 호출합니다.
#include <stdio.h>
#include <stdlib.h>
typedef double(*fun)(double, double);
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double mul(double a, double b) {
return a * b;
}
fun decider(char op) {
switch(op) {
case '+': return add;
case '-': return sub;
case '*': return mul;
}
exit(1);
}
int main(void)
{
fun foo;
foo = decider('+');
printf("%f\n", foo(42.0, 24.0));
foo = decider('-');
printf("%f\n", foo(42.0, 24.0));
foo = decider('*');
printf("%f\n", foo(42.0, 24.0));
return 0;
}
프로그램 출력:
66.000000
18.000000
1008.000000
편집: @dbush 답변 아래에 있는 설명에 따라 이 버전은 다음 단계로 이동합니다.typedef포인터로서, 단지 함수에.동일한 출력을 제공하지만,decider()그것은 내가 쓰든 쓰든 상관없이 깨끗하게 컴파일되고 정확한 출력을 제공합니다.return add;또는return &add;
#include <stdio.h>
#include <stdlib.h>
typedef double(fun)(double, double);
double add(double a, double b) {
return a + b;
}
double sub(double a, double b) {
return a - b;
}
double mul(double a, double b) {
return a * b;
}
fun *decider(char op) {
switch(op) {
case '+': return add; // return &add;
case '-': return sub;
case '*': return mul;
}
exit(1);
}
int main(void)
{
fun *foo;
foo = decider('+');
printf("%f\n", foo(42.0, 24.0));
foo = decider('-');
printf("%f\n", foo(42.0, 24.0));
foo = decider('*');
printf("%f\n", foo(42.0, 24.0));
return 0;
}
C에서 포인터를 함수로 되돌릴 수 있지만, 그러기 위해서는 함수가 먼저 존재해야 하며 동적으로 함수를 만드는 것은 C가 가능하다고 말하는 것이 아닙니다. 어떻게 하는지는 신경쓰지 마세요.
코드가 하나의 OS와 하나의 프로세서에서만 작동하는 경우(아마도 일부 다른 제한 사항), 다음과 같은 작업을 수행할 수 있습니다.
- 메모리 페이지 할당
- 당신이 원하는 것을 하는 쓰기 데이터와 기계 코드, 포인터에 의해 전달되는 호출 기능 등.
- 메모리 보호를 읽기/쓰기에서 읽기/쓰기로 변경
- 생성된 함수로 포인터 반환
- 기능당 4kB가 필요하다고 걱정하지 마십시오.
아마도 그것을 위한 도서관이 어딘가에 있을 것이지만, 꼭 휴대할 수는 없습니다.
그래서 당신은 함수에 포인터를 반환하는 함수를 원합니다.
double retfunc()
{
return 0.5;
}
double (*fucnt)()
{
return retfunc;
}
main()
{
printf("%f\n", (*funct())());
}
일부 사람들은 이 문제를 해결하기 위해 해킹을 쓰는 것에 대해 분명히 편집증적이기 때문에, 덜 해킹적인 방법이 있습니다: setjmp와 longjmp가 있는 정적 구조를 사용하세요.
jmp_buf jb;
void *myfunc(void) {
static struct {
// put all of your local variables here.
void *new_data, *data;
int i;
} *_;
_ = malloc(sizeof(*_));
_.data = _;
if (!(_.new_data = (void *)(intptr_t)setjmp(jb)))
return _.data;
_.data = _.new_data;
/* put your code here */
free(_);
return NULL;
}
여기서 무슨 일이 일어나고 있는지 설명하기 위해 점프 버퍼가 생성될 때 setjmp는 0의 값을 반환합니다. 그렇지 않으면 longjmp(jb, 5)가 전달한 값을 반환합니다(예: longjmp(jb, 5)가 setjmp를 5를 반환합니다.
그래서 우리가 하고 있는 일은 우리의 함수가 할당된 데이터 구조에 대한 포인터를 반환하도록 한 다음, 우리의 종결을 다음과 같이 부르는 것입니다.
void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);
int가 모든 플랫폼에 포인터를 저장할 수 있을 정도로 충분히 큰 것은 아니므로 데이터 풀을 생성하고 핸들로 데이터를 반환/전달해야 할 수 있습니다(풀의 인덱스).
제가 전에 말했듯이, 폐쇄는 모든 데이터가 힙에 할당된 기능일 뿐입니다.
저는 수년 동안 N64와 PSP 게임을 위해 해킹을 써왔습니다.이것이 불가능하다고 주장하는 사람들은 결코 그런 종류의 것들과 엮이지 않았을 것입니다.대부분은 경험 부족으로 귀결됩니다.
난 여기서 엄청 화낼 테니까, 엉덩이를 꽉 잡아요.
표준 C API는 다음과 같은 두 가지 기능을 제공합니다.setjmp그리고.longjmp이름을 잘못 짓는 것은 차치하고, 기본적으로 현재 상태의 복사본(스택 위치 및 레지스터 값 포함)을 저장하는 것입니다.jmp_buf(또는 기술 이름, a)continuation).
이제 함수를 만든다고 가정해 보겠습니다.
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
스펀을 호출하면 스택 프레임이 생성됩니다.이 함수에 대한 인수가 없으므로 스택의 첫 번째 항목은 반환 주소가 되고 바로 다음에 sp_minus1 개체가 됩니다.
왜 이것이 관련이 있습니까?sp_minus1의 주소는 스택 프레임의 시작과 관련이 있습니다.스택 프레임의 주소를 찾을 수 있는 경우jb바꿀 수 있어요...힙에 있는 위치로?
이 시점에서 우리가 가진 것은 힙에서 긴 jmp 함수 호출을 위한 스택 프레임을 만드는 방법입니다. 이는 호출된 컨텍스트에 대한 추가 상태를 포함할 수 있습니다. 즉, 폐쇄입니다.
이런 식으로 longjmp/setjmp를 사용하는 사람을 본 적이 없는 것 같은데, C에서 동적으로 함수를 생성하고 반환하는 방법을 찾고 있다면 이 방법이 가장 좋은 경로일 것 같습니다.
편집:
다음은 제가 설명하는 해킹의 구현 예입니다.
#include <inttypes.h> // intptr_t
#include <setjmp.h> // longjmp, setjmp
#include <stdio.h> // printf
#include <stdlib.h> // malloc, free
#include <string.h> // memcpy
typedef struct {
jmp_buf jb;
int fixupc;
int fixupv[10];
size_t stack_size; // this is only an approximation
void *stack_ptr;
} CLOSURE;
int getclosure(CLOSURE *closure) {
unsigned int i, size;
void *i_ptr = &i, *sp;
unsigned char *data = (unsigned char *)(void *)closure->jb;
memset(closure, 0, sizeof(CLOSURE));
if (!setjmp(closure->jb)) {
printf("looking for 0x%08X...\n\n", (unsigned int)(intptr_t)i_ptr);
for (i = 0; i < sizeof(closure->jb); i++) {
memcpy(&sp, &data[i], sizeof(void *));
size = (unsigned int)(intptr_t)(sp - i_ptr);
if (size < 0x300) {
closure->fixupv[closure->fixupc++] = i;
printf(" fixup @ 0x%08X\n", (unsigned int)(intptr_t)sp);
if (sp > closure->stack_ptr) {
closure->stack_size = size;
closure->stack_ptr = sp;
}
}
}
if (!closure->stack_ptr)
return 0;
printf("\nsp @ 0x%08X\n", (unsigned int)(intptr_t)closure->stack_ptr);
printf("# of fixups = %i\n", closure->fixupc);
/*
* once we allocate the new stack on the heap, we'll need to fixup
* any additional stack references and memcpy the current stack.
*
* for the sake of this example, I'm only fixing up the pointer
* to the stack itself.
*
* after that, we would have successfully created a closure...
*/
closure->stack_size = 1024;
sp = malloc(closure->stack_size);
memcpy(sp, closure->stack_ptr, closure->stack_size);
memcpy(&data[closure->fixupv[0]], &sp, sizeof(void *));
closure->stack_ptr = sp;
return 1;
} else {
/*
* to this bit of code right here
*/
printf("holy shit!\n");
return 0;
};
}
void newfunc(CLOSURE *closure) {
longjmp(closure->jb, 1);
}
void superfunc(CLOSURE *closure) {
newfunc(closure);
}
int main(int argc, char *argv[]) {
CLOSURE c;
if (getclosure(&c)) {
printf("\nsuccess!\n");
superfunc(&c);
free(c.stack_ptr);
return 0;
}
return 0;
}
이것은 기술적으로 스택 스매싱의 한 형태이므로 기본적으로 GCC는 프로그램을 중단시키는 스택 카나리아를 생성합니다.'-fno-stack-protection'으로 컴파일하면 작동합니다.
언급URL : https://stackoverflow.com/questions/37767634/how-can-i-make-a-function-that-returns-a-function
'programing' 카테고리의 다른 글
| 특수 문자가 포함된 Mysql 명령줄 암호가 작동하지 않습니다. (0) | 2023.07.28 |
|---|---|
| Oracle SQL 쿼리를 사용하여 그룹의 각 요소에 대한 시퀀스 번호 추가 (0) | 2023.07.28 |
| MySQL에서 지정된 텍스트가 포함된 필드를 쿼리하려면 어떻게 해야 합니까? (0) | 2023.07.28 |
| 주요 가치 관찰을 수행하고 UIView 프레임에서 KBO 콜백을 받으려면 어떻게 해야 합니까? (0) | 2023.07.28 |
| Node.js - 생성자로 module.exports 사용 (0) | 2023.07.28 |