如何动态创建一个函数?

18
我试图将我的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手册(允许嵌套函数)GCC manual说:
“如果在包含函数退出后尝试通过其地址调用嵌套函数,将会出现一片混乱。”
这是我对这段代码的预期。
function * createConstantFunction(const double value){
 double function(unsigned int,unsigned int){
   return value;
 }
 return &function;
}

怎样才能让它工作起来?

你只限于使用C语言吗?如果使用C++的模板,会对你有所帮助。 - Alex Deem
是的,我有点卡在C语言上了。 尝试做一些有趣的事情,但我想我只能用丑陋的方法去做了。感谢你们的回答! - nick
4
总的来说,这可能是解决问题的错误方法(所以我不会将其作为答案),但是可以看一下libtcc:http://bellard.org/tcc/ - 它是一个相对较小的(约100Kb)可嵌入的C编译器,你可以将C源码输入其中,它将返回在内存中编译的代码的函数指针。 - Pavel Minaev
11个回答

24

C是一种编译语言,你不能在运行时“创建C代码”,它没有直接支持将指令发射到内存等功能。你当然可以尝试分配内存,确保其可执行性,并向其中发射原始的机器码,然后使用适当的函数指针从C中调用它。

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


12
我猜你对一些支持闭包机制的编程语言应该很熟悉吧? 不幸的是,C语言本身不支持像那样的闭包。
如果你坚持要使用闭包,你可以找到一些有用的库来模拟闭包在C语言中的实现。但是大多数这些库都比较复杂且与机器相关。 或者,如果你能改变double ()(unsigned,unsigned)的签名,你也可以改变主意,接受C风格的闭包。
在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)对,像&constant_function,&context这样的是C风格的闭包。 每个需要闭包的函数(F)都必须有一个上下文参数,该参数将作为上下文传递给闭包。 调用F的调用者必须使用正确的(f,c)对。
如果您可以更改函数的签名以适应C风格的闭包,那么您的代码将简单且与机器无关。 如果您不能(函数和应用程序不是由您编写的),请尝试说服作者更改该代码。 如果不可能,您别无选择,只能使用一些闭包库。

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

7
使用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,因此未经测试,但我记得它的工作方式大致如下。)

这看起来像是正确的答案。不过FFCALL的文档有点欠缺。 - user48956

5

一种方法是编写一个包含所需函数集的标准 C 文件,通过 gcc 编译并将其作为动态库加载,以获取指向这些函数的指针。

最终,如果您能够在不需要实时定义它们的情况下指定您的函数(例如通过具有接受参数以定义其特定行为的通用模板函数),那么可能会更好。


4
如果你想要即时编写代码并执行,nanojit可能是一个不错的选择。
在你上面的代码中,你试图创建一个闭包。C语言不支持这个功能。虽然有一些可怕的方法可以模拟它,但是在默认情况下,你将无法将变量绑定到函数中。

2
unwind所提到的,"在运行时创建代码"不被该语言支持,并且需要大量工作。
我自己没有使用过,但是我的一个同事非常喜欢Lua,它是一种"嵌入式语言"。有一个Lua C API,理论上至少可以让您执行动态(脚本化)操作。
当然,缺点是最终用户可能需要接受Lua培训。
这可能是一个愚蠢的问题,但为什么函数必须在您的应用程序中生成?同样,最终用户从自动生成函数中获得什么优势(与您提供的一个或多个预定义函数进行选择相比)?

1

这种机制被称为反射,它允许代码在运行时修改自身的行为。Java支持反射API来完成这项工作。
但我认为C语言中并没有这种支持。

Sun网站表示: 反射是强大的,但不应该被滥用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。访问代码时应注意以下问题。
反射的缺点 性能开销:由于反射涉及动态解析的类型,因此某些Java虚拟机优化无法执行。因此,反射操作比其非反射对应操作具有较慢的性能,并且应避免在性能敏感应用程序中频繁调用的代码段中使用。
安全限制:反射需要运行时权限,当在安全管理器下运行时可能不存在该权限。这是在必须在受限安全上下文中运行的代码(例如Applet)中的重要考虑因素。
内部暴露:由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,因此使用反射可能会导致意外的副作用,这可能使代码失效并破坏可移植性。反射代码打破了抽象,因此可能会随着平台升级而改变行为。

1

看起来你来自另一种常用这种类型代码的语言。C语言不支持它,虽然你可以编写一些动态生成代码的东西,但很可能这并不值得努力。

相反,你需要做的是向函数添加一个额外的参数,引用它应该处理的矩阵。这很可能是支持动态函数的语言内部所做的。


1
如果你真的需要动态创建函数,也许嵌入式C解释器可以帮助你。我刚刚搜索了一下“嵌入式C解释器”,得到了Ch作为结果。

http://www.softintegration.com/

我从未听说过它,所以对它一无所知,但似乎值得一看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接