是否可以在C ++运行期间动态创建函数?

时间:2012-06-13 13:38:36

标签: c++ function dynamic runtime

C ++是一种静态的编译语言,模板在编译期间解析等等......

但是有可能在运行时创建一个函数,这在源代码中没有描述,并且在编译期间没有转换为机器语言,因此用户可以抛出源代码中没有预料到的数据。 ?

我知道这不可能以一种简单的方式发生,但肯定必须是可能的,有很多编程语言没有被编译并且动态地创建那些用C或C ++实现的东西。

也许如果创建了所有原始类型的工厂,以及将它们组织成更复杂的对象(如用户类型和函数)的合适数据结构,这是可以实现的吗?

欢迎提供有关该主题的任何信息以及指向在线资料的链接。谢谢!

编辑:我知道这是可能的,更像是我对实现细节感兴趣:)

14 个答案:

答案 0 :(得分:43)

,当然,没有任何工具在其他答案中提到,但只是使用C ++编译器

只需在C ++程序中执行这些步骤(在Linux上,但在其他操作系统上必须类似)

  1. 使用ofstream
  2. 将C ++程序写入文件(例如在/tmp/prog.cc中)
  3. 通过system("c++ /tmp/prog.cc -o /tmp/prog.so -shared -fPIC");
  4. 编译程序
  5. 动态加载程序,例如使用dlopen()

答案 1 :(得分:38)

您也可以直接将字节码提供给函数,并将其作为函数类型传递给它,如下所示。

e.g。

byte[3] func = { 0x90, 0x0f, 0x1 }
*reinterpret_cast<void**>(&func)()

答案 2 :(得分:15)

是的,JIT编译器会一直这样做。它们分配一块由OS赋予特殊执行权限的内存,然后用代码填充它并将指针强制转换为函数指针并执行它。很简单。

编辑:以下是有关如何在Linux中执行此操作的示例:http://burnttoys.blogspot.de/2011/04/how-to-allocate-executable-memory-on.html

答案 3 :(得分:8)

下面是基于前面提到的方法的C ++运行时编译示例(将代码写入输出文件,通过system()编译,通过dlopen()dlsym()加载)。另请参阅related question中的示例。这里的区别在于它动态编译类而不是函数。这是通过向要动态编译的代码添加C样式maker()函数来实现的。参考文献:

该示例仅适用于Linux(Windows具有LoadLibraryGetProcAddress函数),并且需要在目标计算机上提供相同的编译器。

<强> baseclass.h

#ifndef BASECLASS_H
#define BASECLASS_H
class A
{
protected:
    double m_input;     // or use a pointer to a larger input object
public:
    virtual double f(double x) const = 0;
    void init(double input) { m_input=input; }
    virtual ~A() {};
};
#endif /* BASECLASS_H */

<强>的main.cpp

#include "baseclass.h"
#include <cstdlib>      // EXIT_FAILURE, etc
#include <string>
#include <iostream>
#include <fstream>
#include <dlfcn.h>      // dynamic library loading, dlopen() etc
#include <memory>       // std::shared_ptr

// compile code, instantiate class and return pointer to base class
// https://www.linuxjournal.com/article/3687
// http://www.tldp.org/HOWTO/C++-dlopen/thesolution.html
// https://stackoverflow.com/questions/11016078/
// https://stackoverflow.com/questions/10564670/
std::shared_ptr<A> compile(const std::string& code)
{
    // temporary cpp/library output files
    std::string outpath="/tmp";
    std::string headerfile="baseclass.h";
    std::string cppfile=outpath+"/runtimecode.cpp";
    std::string libfile=outpath+"/runtimecode.so";
    std::string logfile=outpath+"/runtimecode.log";
    std::ofstream out(cppfile.c_str(), std::ofstream::out);

    // copy required header file to outpath
    std::string cp_cmd="cp " + headerfile + " " + outpath;
    system(cp_cmd.c_str());

    // add necessary header to the code
    std::string newcode =   "#include \"" + headerfile + "\"\n\n"
                            + code + "\n\n"
                            "extern \"C\" {\n"
                            "A* maker()\n"
                            "{\n"
                            "    return (A*) new B(); \n"
                            "}\n"
                            "} // extern C\n";

    // output code to file
    if(out.bad()) {
        std::cout << "cannot open " << cppfile << std::endl;
        exit(EXIT_FAILURE);
    }
    out << newcode;
    out.flush();
    out.close();

    // compile the code
    std::string cmd = "g++ -Wall -Wextra " + cppfile + " -o " + libfile
                      + " -O2 -shared -fPIC &> " + logfile;
    int ret = system(cmd.c_str());
    if(WEXITSTATUS(ret) != EXIT_SUCCESS) {
        std::cout << "compilation failed, see " << logfile << std::endl;
        exit(EXIT_FAILURE);
    }

    // load dynamic library
    void* dynlib = dlopen (libfile.c_str(), RTLD_LAZY);
    if(!dynlib) {
        std::cerr << "error loading library:\n" << dlerror() << std::endl;
        exit(EXIT_FAILURE);
    }

    // loading symbol from library and assign to pointer
    // (to be cast to function pointer later)
    void* create = dlsym(dynlib, "maker");
    const char* dlsym_error=dlerror();
    if(dlsym_error != NULL)  {
        std::cerr << "error loading symbol:\n" << dlsym_error << std::endl;
        exit(EXIT_FAILURE);
    }

    // execute "create" function
    // (casting to function pointer first)
    // https://stackoverflow.com/questions/8245880/
    A* a = reinterpret_cast<A*(*)()> (create)();

    // cannot close dynamic lib here, because all functions of the class
    // object will still refer to the library code
    // dlclose(dynlib);

    return std::shared_ptr<A>(a);
}


int main(int argc, char** argv)
{
    double input=2.0;
    double x=5.1;
    // code to be compiled at run-time
    // class needs to be called B and derived from A
    std::string code =  "class B : public A {\n"
                        "    double f(double x) const \n"
                        "    {\n"
                        "        return m_input*x;\n"
                        "    }\n"
                        "};";

    std::cout << "compiling.." << std::endl;
    std::shared_ptr<A> a = compile(code);
    a->init(input);
    std::cout << "f(" << x << ") = " << a->f(x) << std::endl;

    return EXIT_SUCCESS;
}

<强>输出

$ g++ -Wall -std=c++11 -O2 -c main.cpp -o main.o   # c++11 required for std::shared_ptr
$ g++ -ldl main.o -o main
$ ./main
compiling..
f(5.1) = 10.2

答案 4 :(得分:5)

除了简单地使用嵌入式脚本语言(Lua非常适合嵌入)或编写自己的C ++编译器以便在运行时使用,如果你真的想使用C ++,你可以使用现有的编译器。

例如Clang是一个构建为库的C ++编译器,可以很容易地嵌入到另一个程序中。它被设计用于需要以各种方式分析和操作C ++源代码的IDE等程序,但是使用LLVM编译器基础结构作为后端,它还能够在运行时生成代码并为您提供函数您可以调用以运行生成的代码的指针。

答案 5 :(得分:4)

基本上你需要在你的程序中编写一个C ++编译器(不是一个简单的任务),并且做JIT编译器运行代码所做的事情。实际上,您使用此段落的方式占了90%:

  

我知道这不可能以直截了当的方式发生,但肯定是这样   必须是可能的,有很多编程语言   没有编译并动态创建那种东西   用C或C ++实现。

确切地说 - 那些程序随身携带翻译。你运行一个python程序,说python MyProgram.py - python是编译后的C代码,它能够动态地解释和运行你的程序。你需要在这些方面做一些事情,但是使用C ++编译器。

如果您需要 ,请使用其他语言:)

答案 6 :(得分:4)

看看libtcc;它简单,快速,可靠,适合您的需求。每当我需要“动态”编译C函数时,我就会使用它。

在存档中,您将找到文件 examples / libtcc_test.c ,这可以为您提供良好的开端。 这个小教程也可以帮助您:http://blog.mister-muffin.de/2011/10/22/discovering-tcc/

如果您在使用该库时遇到任何问题,请在评论中提出问题!

答案 7 :(得分:2)

一种典型的方法是将C ++(或其编写的任何内容)项目与脚本语言结合起来。
Lua是最受欢迎的产品之一,因为它有很好的文档记录,很小,并且有许多语言的绑定。

但是如果你没有考虑那个方向,也许你可以考虑使用动态库吗?

答案 8 :(得分:1)

是的 - 您可以在C ++中编写C ++编译器,并提供一些额外的功能 - 编写自己的函数,自动编译和运行(或不编译)......

答案 9 :(得分:1)

了解.NET中的ExpressionTrees - 我认为这基本上就是您想要实现的目标。创建一个子表达式树,然后评估它们。在面向对象的方式中,可能的每个节点都知道如何通过递归到其子节点来评估自身。然后,您的可视语言将创建此树,您可以编写一个简单的解释器来执行它。

另外,请查看Ptolemy II,作为Java中关于如何编写这种可视化编程语言的示例。

答案 10 :(得分:1)

您可以查看Runtime Compiled C++(或查看RCC++ blog and videos),或者尝试使用alternatives之一。

答案 11 :(得分:0)

这样对我有用。您必须使用-fpermissive标志。 我正在使用CodeBlocks 17.12。

#include <cstddef>

using namespace std;
int main()
{
    char func[] = {'\x90', '\x0f', '\x1'};
    void (*func2)() = reinterpret_cast<void*>(&func);
    func2();
    return 0;
}

答案 12 :(得分:0)

使用操作码扩展 Jay's answer,以下适用于 Linux。

  1. 从编译器中学习操作码:
    • 写自己的myfunc.cpp,例如
      double f(double x) { return x*x; }
      
    • 编译
      $ g++ -O2 -c myfunc.cpp
      
    • 反汇编函数f
      $ gdb -batch -ex "file ./myfunc.o" -ex "set disassembly-flavor intel" -ex "disassemble/rs f"
      Dump of assembler code for function _Z1fd:
         0x0000000000000000 <+0>:     f2 0f 59 c0     mulsd  xmm0,xmm0
         0x0000000000000004 <+4>:     c3      ret    
      End of assembler dump.
      
      这意味着汇编中的函数 x*xmulsd xmm0,xmm0ret 和机器代码 f2 0f 59 c0 c3
  2. 用机器代码编写您自己的函数:
    • opcode.cpp
      #include <cstdlib>          // EXIT_FAILURE etc
      #include <cstdio>           // printf(), fopen() etc
      #include <cstring>          // memcpy()
      #include <sys/mman.h>       // mmap()
      
      // allocate memory and fill it with machine code instructions
      // returns pointer to memory location and length in bytes
      void* gencode(size_t& length)
      {
          // machine code
          unsigned char opcode[] = {
              0xf2, 0x0f, 0x59, 0xc0,         // mulsd  xmm0,xmm0
              0xc3                            // ret
          };
          // allocate memory which allows code execution
          // https://en.wikipedia.org/wiki/NX_bit
          void* buf = mmap(NULL,sizeof(opcode),PROT_READ|PROT_WRITE|PROT_EXEC,
                           MAP_PRIVATE|MAP_ANON,-1,0);
          // copy machine code to executable memory location
          memcpy(buf, opcode, sizeof(opcode));
          // return: pointer to memory location with executable code
          length = sizeof(opcode);
          return buf;
      }
      
      // print the disassemby of buf
      void print_asm(const void* buf, size_t length)
      {
          FILE* fp = fopen("/tmp/opcode.bin", "w");
          if(fp!=NULL) {
              fwrite(buf, length, 1, fp);
              fclose(fp);
          }
          system("objdump -D -M intel -b binary -mi386 /tmp/opcode.bin");
      }
      
      int main(int, char**)
      {
          // generate machine code and point myfunc() to it
          size_t length;
          void* code=gencode(length);
          double (*myfunc)(double);   // function pointer
          myfunc = reinterpret_cast<double(*)(double)>(code);
      
          double x=1.5;
          printf("f(%f)=%f\n", x,myfunc(x));
          print_asm(code,length);     // for debugging
          return EXIT_SUCCESS;
      }
      
      
    • 编译运行
      $ g++ -O2 opcode.cpp -o opcode
      $ ./opcode
      f(1.500000)=2.250000
      
      /tmp/opcode.bin:     file format binary
      
      
      Disassembly of section .data:
      
      00000000 <.data>:
         0:   f2 0f 59 c0             mulsd  xmm0,xmm0
         4:   c3                      ret    
      

答案 13 :(得分:-1)

如果您不寻求性能,最简单的解决方案是嵌入脚本语言解释器,例如:适用于LuaPython