【问题标题】:Dynamically creating functions in C在 C 中动态创建函数
【发布时间】:2010-12-22 20:03:49
【问题描述】:

如何在 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;
}

那么我怎样才能让它工作呢?

谢谢!

【问题讨论】:

  • 你仅限于c吗? c++ 模板会帮助你。
  • 是的,我有点坚持使用 C。试图做一些有趣的事情来改变,但我想我会选择丑陋的方式。感谢您的回答!
  • 从广义上讲,这可能是解决问题的错误方法(因此我不将其作为答案),但看看 libtcc:bellard.org/tcc - 这是一个相对较小的(~ 100Kb) 可嵌入的 C 编译器,您可以向其中提供 C 源代码,它会返回指向内存中已编译代码的函数指针。

标签: c dynamic


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    你必须熟悉一些支持闭包机制的编程语言,不是吗? 不幸的是,C 本身不支持这样的闭包。

    如果你坚持使用闭包,你可以找到一些有用的库来模拟 C 中的闭包。但是这些库中的大多数都很复杂并且依赖于机器。
    或者,如果您可以更改double ()(unsigned,unsigned); 的签名,您可以改变主意同意C-style closure

    在 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 like &amp;constant_function,&amp;context 就是C-style closure
    每个需要闭包的函数(F)都必须有一个上下文参数,该参数将作为其上下文传递给闭包。 并且 F 的调用者必须使用正确的 (f,c) 对。

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

    【讨论】:

      【解决方案3】:

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

      这种方法感觉有点老套,所以我不建议在生产代码中使用它。由于使用了内联汇编,此解决方案仅适用于 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;
      }
      

      【讨论】:

        【解决方案4】:

        使用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 的文档有点欠缺。
        【解决方案5】:

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

        最终,如果您能够指定您的函数而不必即时定义它们(例如通过一个通用模板函数,该函数接受定义其特定行为的参数),这可能会更好。

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

            正如unwind 已经提到的那样,该语言不支持“在运行时创建代码”,而且工作量很大。

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

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

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

            【讨论】:

              【解决方案8】:

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

              Sun 网站说:

              反射很强大,但不应该 被乱用。如果是 可以执行操作 不使用反射,那么它是 最好避免使用它。这 以下问题应保留在 访问代码时请注意 反射。

              反射的缺点

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

              安全限制

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

              内部结构暴露

              由于反射允许代码 执行将是 在非反射代码中是非法的,例如 作为访问私有字段和 方法,使用反射可以 导致意想不到的副作用, 这可能会使代码功能失调 并可能破坏便携性。 反射代码打破了抽象 因此可能会改变行为 平台的升级。 .

              【讨论】:

                【解决方案9】:

                您似乎来自另一种您经常使用此类代码的语言。 C 不支持它,尽管您当然可以编写一些东西来动态生成代码,但这很可能不值得。

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

                【讨论】:

                  【解决方案10】:

                  如果您确实需要动态创建函数,也许嵌入式 C 解释器会有所帮助。我刚刚搜索了“嵌入式 C 解释器”,结果得到了 Ch:

                  http://www.softintegration.com/

                  没听说过,所以不知道,不过好像值得一看。

                  【讨论】:

                    猜你喜欢
                    • 2014-12-21
                    • 1970-01-01
                    • 2021-09-28
                    • 2016-06-09
                    • 2013-12-06
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-02-09
                    • 1970-01-01
                    相关资源
                    最近更新 更多