在C中动态创建函数

时间:2009-12-03 13:50:18

标签: 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);

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

  

“如果您尝试通过其后的地址调用嵌套函数   包含功能已经退出,所有地狱都会破裂。“

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

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

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

谢谢!

10 个答案:

答案 0 :(得分:20)

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

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

答案 1 :(得分: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
}

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这样的{p> &constant_function,&contextC-style closure 需要闭包的每个函数(F)必须有一个上下文参数,该参数将作为其上下文传递给闭包。 并且F的调用者必须使用正确的(f,c)对。

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

答案 2 :(得分:5)

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

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

答案 3 :(得分: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;
}

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

答案 4 :(得分:4)

如果您想动态编写代码以便执行,nanojit可能是一个很好的方法。

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

答案 5 :(得分:2)

正如unwind已经提到的那样,语言不支持“在运行时创建代码”,并且需要做很多工作。

我自己没有使用它,但我的一位同事发誓Lua是一种“嵌入式语言”。有一个Lua C API(理论上至少)允许你执行动态(脚本)操作。

当然,缺点是最终用户可能需要在Lua进行某种培训。

这可能是一个愚蠢的问题,但为什么必须在您的应用程序中生成该函数?类似地,最终用户自己生成函数有什么优势(而不是从您提供的一个或多个预定义函数中进行选择)?

答案 6 :(得分:2)

因为你想要生成一个遵循简单配方的函数, 对于某些内联汇编而言,这不应该太棘手 一块可执行/可写内存。

这种方法感觉有点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 :(得分:1)

此机制称为反射,其中代码在运行时修改其自身的行为。 Java支持reflection api来完成这项工作 但我认为这种支持在C中不可用。

  
    

Sun网站上说:

         

反思很有力,但不应该     不分青红皂白地使用。如果是     可以执行操作     没有使用反射,那就是     最好避免使用它。该     应关注以下问题     通过访问代码时的心态     反射。

         

反思的缺点

         

效果开销因为     反思涉及的类型     动态解决,某些Java     虚拟机优化不能     被执行。因此,反思     操作性能较慢     比他们的不反光     同行,应该避免     被调用的代码段     经常对性能敏感     应用。

  
     

安全限制

     
    

反射需要运行时     许可可能不存在     在安全管理器下运行时。     这是一个重要的考虑因素     对于必须在a中运行的代码     受限制的安全上下文,例如     在一个小程序中。

  
     

内部曝光

     
    

因为反射允许代码     执行将要执行的操作     非反射性代码中的非法行为     作为访问私人领域和     方法,使用反射即可     导致意想不到的副作用,     这可能会导致代码功能失调     并可能破坏可移植性。     反射代码打破了抽象     因此可能改变行为     升级平台。 。

  

答案 8 :(得分:1)

看起来你是来自另一种常用这类代码的语言。 C不支持它,虽然你当然可以做一些事情来动态生成代码,但很可能这不值得付出努力。

你需要做的是在函数中添加一个额外的参数来引用它应该处理的矩阵。这很可能是支持动态函数的语言无论如何都会在内部完成的。

答案 9 :(得分:1)

如果您确实需要动态创建函数,那么嵌入式C解释器可能会有所帮助。我只是搜索了“嵌入式C解释器”并得到了Ch:

http://www.softintegration.com/

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