Динамически создаваемые функции в C - PullRequest
14 голосов
/ 03 декабря 2009

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

Я пытаюсь обобщить мою проблему C следующим образом:

  • У меня есть матрица, и я хочу использовать некоторую функцию для генерации ее элементов.

  • функция не имеет аргументов

Следовательно, я определяю следующее:

typedef double(function)(unsigned int,unsigned int);

/* writes f(x,y) to each element x,y of the matrix*/
void apply(double ** matrix, function * f);

Теперь мне нужно генерировать постоянные функции в коде. Я думал о создании вложенной функции и возвращении ее указателя, но в руководстве GCC (которое допускает вложенные функции) говорится:

"Если вы попытаетесь вызвать вложенную функцию через ее адрес после содержащая функция вышла, весь ад вырвется на свободу. "

чего бы я ожидал от этого кода ...

function * createConstantFunction(const double value){
 double function(unsigned int,unsigned int){
   return value;
 }
 return &function;
}

Так как мне заставить его работать?

Спасибо!

Ответы [ 10 ]

20 голосов
/ 03 декабря 2009

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

Вы не получите никакой помощи от самого языка, это все равно, что генерировать код и вызывать его в BASIC на старой 8-битной машине.

8 голосов
/ 04 декабря 2009

Вы должны быть знакомы с некоторым языком программирования, который поддерживает механизм закрытия, не так ли? К сожалению, C не поддерживает закрытие как таковое.

Вы можете найти несколько полезных библиотек, которые имитируют закрытие в C, если вы настаивали на закрытии. Но большинство этих библиотек сложны и зависят от машины.
Кроме того, вы можете передумать согласиться с C-style closure, если вы можете изменить подпись double ()(unsigned,unsigned);.

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

// first, add one extra parameter in the signature of function.
typedef double(function)(double extra, unsigned int,unsigned int);

// second, add one extra parameter in the signature of apply
void apply(double* matrix,unsigned width,unsigned height, function* f, double extra)
{
        for (unsigned y=0; y< height; ++y)
            for (unsigned x=0; x< width ++x)
                    matrix[ y*width + x ] = f(x, y, extra);
        // apply will passing extra to f
}

// third, in constant_function, we could get the context: double extra, and return it
double constant_function(double value, unsigned x,unsigned y) { return value; }

void test(void)
{
        double* matrix = get_a_matrix();
        // fourth, passing the extra parameter to apply
        apply(matrix, w, h, &constant_function, 1212.0);
        // the matrix will be filled with 1212.0
}

Достаточно ли double extra? Да, но только в этом случае.
Как нам поступить, если требуется больше контекста?
В C параметр общего назначения - void*, мы можем передать любой контекст через один параметр void *, передав адрес контекста.

Вот еще один пример:

typedef double (function)(void* context, int, int );
void apply(double* matrix, int width,int height,function* f,void* context)
{
        for (int y=0; y< height; ++y)
            for (int x=0; x< width ++x)
                    matrix[ y*width + x ] = f(x, y, context); // passing the context
}
double constant_function(void* context,int x,int y)
{
        // this function use an extra double parameter \
        //    and context points to its address
        double* d = context;
        return *d;
}
void test(void)
{
        double* matrix = get_a_matrix();
        double context = 326.0;
        // fill matrix with 326.0
        apply( matrix, w, h, &constant_function, &context);
}

(function,context) pair как &constant_function,&context это C-style closure.
Каждая функция (F), которая нуждается в закрытии, должна иметь один параметр контекста, который будет передан закрытию в качестве контекста. И вызывающий F должен использовать правильную (f, c) пару.

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

5 голосов
/ 03 декабря 2009

Использование FFCALL , который обрабатывает специализированный трюк для выполнения этой работы:

#include <stdio.h>
#include <stdarg.h>
#include <callback.h>

static double internalDoubleFunction(const double value, ...) {
    return value;
}
double (*constDoubleFunction(const double value))() {
    return alloc_callback(&internalDoubleFunction, value);
}

main() {
    double (*fn)(unsigned int, unsigned int) = constDoubleFunction(5.0);
    printf("%g\n", (*fn)(3, 4));
    free_callback(fn);
    return 0;
}

(Не проверено, так как в настоящее время у меня не установлен FFCALL, но я помню, что он работает примерно так.)

5 голосов
/ 03 декабря 2009

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

В конечном счете, вероятно, было бы лучше, если бы вы могли определять свои функции без необходимости определять их «на лету» (например, с помощью универсальной функции шаблона, которая принимает аргументы, определяющие ее конкретное поведение).

4 голосов
/ 03 декабря 2009

Если вы хотите написать код на лету для выполнения, nanojit может быть хорошим способом.

В приведенном выше коде вы пытаетесь создать замыкание. С не поддерживает это. Есть несколько отвратительных способов подделать его, но из коробки вы не сможете связать во время выполнения переменную с вашей функцией.

3 голосов
/ 19 июля 2017

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

Этот подход выглядит немного хакерским, поэтому я бы не рекомендовал его в рабочем коде. Из-за использования встроенной сборки это решение работает только на Intel x86-64 / AMD64 и должно быть переведено для работы с другими архитектурами.

Вы можете предпочесть это другим решениям на основе JIT, поскольку это не зависит от какой-либо внешней библиотеки.

Если вам нужно более подробное объяснение того, как работает приведенный ниже код, оставьте комментарий и я его добавлю.

По соображениям безопасности кодовая страница должна быть помечена PROT_READ|PROT_EXEC после создания функции (см. mprotect ).

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/mman.h>

int snippet_processor(char *buffer, double value, int action);

enum snippet_actions {
    S_CALC_SIZE,
    S_COPY,
};

typedef double (*callback_t) (unsigned int, unsigned int);

int main(int argc, char **argv) {

    unsigned int pagesize = 4096;
    char *codepage = 0;
    int snipsz = 0;

    callback_t f;

    /* allocate some readable, writable and executable memory */
    codepage = mmap(codepage,
        pagesize,
        PROT_READ | PROT_WRITE | PROT_EXEC,
        MAP_ANONYMOUS | MAP_PRIVATE,
        0,
        0);

    // generate one function at `codepage` and call it
    snipsz += snippet_processor(codepage, 12.55, S_COPY);
    f = (callback_t) (codepage);
    printf("result :: %f\n", f(1, 2));

    /* ensure the next code address is byte aligned
     * - add 7 bits to ensure an overflow to the next byte.
     *   If it doesn't overflow then it was already byte aligned.
     * - Next, throw away any of the "extra" bit from the overflow,
     *   by using the negative of the alignment value 
     *   (see how 2's complement works.
     */
    codepage += (snipsz + 7) & -8;

    // generate another function at `codepage` and call it
    snipsz += snippet_processor(codepage, 16.1234, S_COPY);
    f = (callback_t) (codepage);
    printf("result :: %f\n", f(1, 2));
}

int snippet_processor(char *buffer, double value, int action) {
    static void *snip_start = NULL; 
    static void *snip_end = NULL; 
    static void *double_start = NULL; 
    static int double_offset_start = 0;
    static int size;

    char *i, *j;
    int sz;

    char *func_start;
    func_start = buffer;

    if (snip_start == NULL) {
        asm volatile(
            // Don't actually execute the dynamic code snippet upon entry
            "jmp .snippet_end\n"

            /* BEGIN snippet */
            ".snippet_begin:\n"
            "movq .value_start(%%rip), %%rax\n"
            "movd %%rax, %%xmm0\n"
            "ret\n"

            /* this is where we store the value returned by this function */
            ".value_start:\n"
            ".double 1.34\n"
            ".snippet_end:\n"
            /* END snippet */

            "leaq .snippet_begin(%%rip), %0\n"
            "leaq .snippet_end(%%rip), %1\n"
            "leaq .value_start(%%rip), %2\n"
            : 
            "=r"(snip_start),
            "=r"(snip_end),
            "=r"(double_start)
        );
        double_offset_start = (double_start - snip_start);
        size = (snip_end - snip_start);
    }

    if (action == S_COPY) {
        /* copy the snippet value */
        i = snip_start;
        while (i != snip_end) *(buffer++) = *(i++); 

        /* copy the float value */
        sz = sizeof(double);
        i = func_start + double_offset_start; 
        j = (char *) &value;

        while (sz--) *(i++) = *(j++); 
    }

    return size;
}
2 голосов
/ 03 декабря 2009

Как уже упоминалось unwind , "создание кода во время выполнения" не поддерживается языком и потребует много работы.

Я сам этим не пользовался, но один из моих сотрудников клянется Луа, «встроенным языком». Существует Lua C API , который (по крайней мере теоретически) позволит вам выполнять динамические (скриптовые) операции.

Конечно, недостатком может быть то, что конечному пользователю может понадобиться какое-то обучение в Lua.

Это может быть глупый вопрос, но почему функция должна генерироваться в вашем приложении? Точно так же, какое преимущество получает конечный пользователь от создания самой функции (в отличие от выбора одной или нескольких предопределенных функций, которые вы предоставляете)?

1 голос
/ 03 декабря 2009

Если вам действительно нужно динамически создавать функции, возможно, вам поможет встроенный интерпретатор Си. Я только что погуглил для "встроенного интерпретатора C" и получил Ch в результате:

http://www.softintegration.com/

Никогда не слышал об этом, поэтому я ничего об этом не знаю, но, похоже, стоит посмотреть.

1 голос
/ 03 декабря 2009

Похоже, вы пришли с другого языка, где вы обычно используете этот тип кода. C не поддерживает это, и хотя вы, конечно, могли бы что-то приготовить для динамического генерирования кода, вполне вероятно, что это не стоит усилий.

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

1 голос
/ 03 декабря 2009

Этот механизм называется отражением, когда код изменяет свое поведение во время выполнения. Java поддерживает отражение API , чтобы сделать эту работу.
Но я думаю, что эта поддержка недоступна в C.

На сайте Sun написано:

Отражение мощно, но не должно быть использованы без разбора. Если это можно выполнить операцию без использования отражения, то это предпочтительнее избегать его использования. следующие проблемы должны быть сохранены в будьте осторожны при доступе к коду через отражение.

Недостатки отражения

Снижение производительности Потому что отражение включает в себя типы, которые динамически разрешается, определенная Java оптимизация виртуальной машины не может быть выполненным. Следовательно, отражающий операции работают медленнее чем их неотражающие коллегами, и их следует избегать в разделы кода, которые называются часто в чувствительных к производительности Приложения.

Ограничения безопасности

Отражение требует времени выполнения разрешение, которое может отсутствовать при работе под менеджером безопасности. Это важное соображение для кода, который должен работать в ограниченный контекст безопасности, такой как в апплете.

Воздействие внутренних органов

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...