在C中动态创建函数

nic*_*ick 14 c dynamic

如何在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);
Run Code Online (Sandbox Code Playgroud)

现在我需要在代码中生成常量函数.我想过创建一个嵌套函数并返回它的指针,但是GCC手册(允许嵌套函数)说:

"如果你试图在包含函数退出后通过其地址调用嵌套函数,那么一切都会崩溃."

我希望从这段代码中得到...

function * createConstantFunction(const double value){
 double function(unsigned int,unsigned int){
   return value;
 }
 return &function;
}
Run Code Online (Sandbox Code Playgroud)

那么我怎样才能让它发挥作用?

谢谢!

unw*_*ind 21

C是一种编译语言.您无法在运行时"在C"中创建代码; 没有特定的C支持向内存发出指令等.您当然可以尝试分配内存,确保它是可执行的,并在那里发出原始机器代码.然后使用合适的函数指针从C调用它.

你不会从语言本身得到任何帮助,这就像生成代码并在旧的8位机器上用BASIC调用它一样.


Own*_*loo 8

你必须熟悉一些支持闭包机制的编程语言,不是吗?不幸的是,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
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

(function,context) pair就像&constant_function,&contextC-style closure.
需要闭包的每个函数(F)必须有一个上下文参数,该参数将作为其上下文传递给闭包.并且F的调用者必须使用正确的(f,c)对.

如果您可以更改函数的签名以适应C风格的闭包,那么您的代码将是简单且与机器无关的.
如果不能(功能和申请不是你写的),试着说服他改变他的代码.
如果失败,您别无选择,只能使用一些闭包库.


chr*_*scz 8

由于您想生成一个遵循简单配方的函数,因此对于一些内联汇编和一块可执行/可写内存来说,这应该不会太棘手。

这种方法感觉有点hacky,所以我不会在生产代码中推荐它。由于使用内联汇编,此解决方案仅适用于 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;
}
Run Code Online (Sandbox Code Playgroud)


Rom*_*ain 5

一种方法是使用您想要的函数集编写标准C文件,通过gcc编译它并将其作为动态库加载以获取指向函数的指针.

最终,如果你能够指定你的函数而不必动态定义它们可能会更好(例如通过具有通用模板函数来获取定义其特定行为的参数).


eph*_*ent 5

使用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;
}
Run Code Online (Sandbox Code Playgroud)

(未经测试,因为我当前没有安装FFCALL,但我记得它的工作原理是这样的.)


pli*_*nth 4

如果您想即时编写代码以供执行,nanojit可能是一个不错的选择。

在上面的代码中,您正在尝试创建一个闭包。C 不支持这一点。有一些令人发指的方法可以伪造它,但是开箱即用,您将无法在运行时将变量绑定到函数中。