Подтвердить что ты не робот

Как я могу создать функцию, которая возвращает функцию?

Большое изображение: у меня есть модуль с функциями и модуль с процедурами и функциями над этими функциями.

Когда я объединяю две функции (из интерфейса функционального модуля):

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

Я не могу заставить его работать правильно, ни сделать правильный "звонок". Я хочу использовать выходную "функцию" в качестве входа в другие функции.

4b9b3361

Ответ 1

Другие ответы правильны и интересны, но вы должны знать, что в портативном C99 существует нет возможности иметь подлинные закрытия как функции C (и это фундаментальное ограничение C). Если вы не знаете, что такое закрытие, прочитайте страницу wiki на них (а также прочитайте SICP, в частности его §1.3). Обратите внимание, однако, что в С++ 11 у вас есть закрытие, используя std:: function и лямбда-выражения. И большинство других языков программирования (Ocaml, Haskell, Javascript, Lisp, Clojure, Python,....) имеют замыкания.

Из-за отсутствия подлинного закрытия в C ( "математически" единственными закрытыми значениями в функциях C являются глобальные или статические переменные или литералы), большинство библиотек, принимающих указатели на C, обеспечивают обработку API обратные вызовы с некоторыми данными клиента (простой пример может быть qsort_r, но более серьезно загляните внутрь GTK). Данные клиента (как правило, непрозрачный указатель) могут использоваться для сохранения закрытых значений. Вы, вероятно, захотите следовать аналогичному соглашению (поэтому систематически передавайте указатель функции как обратные вызовы с некоторыми дополнительными клиентскими данными), поэтому вам нужно будет изменить подписи своих функций C (вместо того, чтобы передавать только сырой указатель функции, вы пройдете как указатель функции, так и некоторые данные клиента в качестве обратного вызова, чтобы "подражать" замыканиям).

Иногда вы можете генерировать функцию C во время выполнения (используя нестандартные функции, возможно, с помощью операционной системы или какой-либо внешней библиотеки). Вы можете использовать некоторую JIT компиляцию, такую ​​как GNU lightning, libjit (оба быстро генерируют некоторый медленный код), asmjit (вы будете генерировать каждую машинную инструкцию явно, и ваша ответственность - испускать быстрый код x86-64), GCCJIT или LLVM (оба находятся выше существующих компиляторов, поэтому их можно использовать для испускания - бит медленно - некоторый оптимизированный код), В системах POSIX и Linux вы также можете испустить некоторый код C в некотором временном файле /tmp/tempcode.c, разбить компиляцию (например, gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so) этого кода в плагин и динамически загружать этот сгенерированный плагин с помощью dlopen (3) и dlsym (3)..

Кстати, мы не знаем, что такое конкретное приложение, которое вы кодируете, но вы можете подумать о внедрении внутри него некоторого интерпретатора, например. Lua или Guile. Затем вы будете использовать и предоставлять обратные вызовы встроенному интерпретатору/интерпретатору.

Ответ 2

При возврате функции из другой функции самый чистый способ сделать это с помощью 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.

EDIT:

Пока вы можете объявить тип следующим образом:

typedef double ftype(double, double);

Вы никогда не сможете напрямую использовать такой тип на практике. Функция не может вернуть функцию (только указатель на функцию), и переменная этого типа не может быть назначена.

Кроме того, вам не нужно явно разыскивать указатель на функцию для вызова функции, поэтому тот факт, что сам указатель скрыт, не является большой проблемой. Это также соглашение для определения указателей функций как typedef. На странице для signal:

   #include <signal.h>

   typedef void (*sighandler_t)(int);

   sighandler_t signal(int signum, sighandler_t handler);

Ответ 3

Вы имеете в виду что-то вроде этого? Функция decider() возвращает указатель на другую функцию, которая затем вызывается.

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

Ответ 4

в C вы можете вернуть указатель на функцию, но для этого функция должна существовать первой, а динамическое создание функций - это не то, что C говорит, возможно, неважно, как это сделать.

если ваш код будет работать только на одной ОС и на одном процессоре (и, возможно, на некоторых других ограничениях), вы можете:

  • выделить страницу памяти
  • записывать данные и машинный код, делая то, что вы хотите, вызывающие функции, переданные указателем и т.д.
  • изменить защиту памяти от чтения/записи до чтения/выполнения
  • возвращает указатель на созданную функцию
  • не беспокойтесь, что вам нужно 4kB за функцию

там, вероятно, есть библиотеки для этого, но обязательно не портативные

Ответ 5

Итак, вы хотите, чтобы функция возвращала указатель на функцию.

double retfunc()
{
   return 0.5;
}

double (*fucnt)()
{
  return retfunc;
}

main()
{
   printf("%f\n", (*funct())());
}

Ответ 6

Так как некоторые люди, по-видимому, параноики о написании взлома для решения этой проблемы, здесь используется менее хакерский способ: использовать статическую структуру с 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 (например, longjmp (jb, 5) приведет к возврату setjmp 5).

Итак, что мы делаем, наша функция возвращает указатель на выделенную структуру данных; а затем назовем наше закрытие следующим:

void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);

Обратите внимание, что int не гарантированно будет достаточно большим для хранения указателя на всех платформах; поэтому вам может понадобиться создать пул данных и вернуть/передать данные по дескриптору (указатель в пуле).

Как я уже говорил, закрытие - это просто функция со всеми данными, выделенными в куче.

Я много лет писал хаки для игр N64 и PSP. Люди, утверждающие, что это невозможно, вероятно, никогда не переделывали подобные вещи. В основном просто сводится к недостатку опыта.

Ответ 7

Я собираюсь стать действительно взломанным здесь, поэтому держитесь за свои бриджи.

Стандартная C api имеет 2 функции, называемые setjmp и longjmp. Плохое именование в сторону, то, что они в основном делают, это сохранить копию текущего состояния (включая положение стека и значения регистра) в jmp_buf (или, техническое имя, continuation).

Теперь, скажем, вы создаете функцию:

jmp_buf jb;

void sfunc(void) {
    void *sp_minus1 = 0xBEEFBABE;
    setjmp(jb);
}

Когда вы вызываете sfunc, будет создан фрейм стека. Поскольку аргументов этой функции нет, первая запись в стеке будет адресом возврата и сразу после того, как она будет объектом sp_minus1.

Почему это актуально? Ну, адрес sp_minus1 относится к началу фрейма стека. Если вы можете найти адрес фрейма стека в jb, вы можете его изменить... скажем, в папку в куче?

То, что у нас есть на данный момент, - это способ создания кадров стека для вызовов функций longjmp в куче, которые могут содержать дополнительное состояние о контексте, в котором они были вызваны; или, другими словами, замыкания.

Я не думаю, что когда-либо видел, что кто-то использует longjmp/setjmp таким образом, но если вы ищете способ динамически генерировать и возвращать функции на C, я думаю, что это будет ваш лучший маршрут.

EDIT:

Вот пример реализации хака, который я описываю:

#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", это сработает.