59

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

但是是否有可能在运行时创建一个在源代码中没有描述并且在编译期间没有转换为机器语言的函数,以便用户可以将源代码中没有预料到的数据扔给它?

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

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

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

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

4

14 回答 14

50

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

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

  1. 将 C++ 程序写入文件(例如在 /tmp/prog.cc 中),使用ofstream
  2. 通过编译程序system("c++ /tmp/prog.cc -o /tmp/prog.so -shared -fPIC");
  3. 动态加载程序,例如使用dlopen()
于 2012-06-13T13:53:46.543 回答
41

您也可以直接将字节码提供给函数,然后将其转换为函数类型,如下所示。

例如

byte[3] func = { 0x90, 0x0f, 0x1 }
*reinterpret_cast<void**>(&func)()
于 2012-06-13T17:36:33.247 回答
20

是的,JIT 编译器一直都在这样做。他们分配一块已被操作系统赋予特殊执行权限的内存,然后用代码填充它并将指针转换为函数指针并执行它。很简单。

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

于 2012-06-13T13:44:49.330 回答
12

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

该示例仅在 Linux 下有效(Windows 具有LoadLibraryGetProcAddress功能),并且需要在目标机器上提供相同的编译器。

基类.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 */

主文件

#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
于 2016-03-30T12:07:33.143 回答
7

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

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

于 2012-06-13T13:51:06.950 回答
7

看看libtcc;它简单、快速、可靠并且适合您的需要。每当我需要“即时”编译 C 函数时,我都会使用它。

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

#include <stdlib.h>
#include <stdio.h>
#include "libtcc.h"

int add(int a, int b) { return a + b; }

char my_program[] =
"int fib(int n) {\n"
"    if (n <= 2) return 1;\n"
"    else return fib(n-1) + fib(n-2);\n"
"}\n"
"int foobar(int n) {\n"
"    printf(\"fib(%d) = %d\\n\", n, fib(n));\n"
"    printf(\"add(%d, %d) = %d\\n\", n, 2 * n, add(n, 2 * n));\n"
"    return 1337;\n"
"}\n";

int main(int argc, char **argv)
{
    TCCState *s;
    int (*foobar_func)(int);
    void *mem;

    s = tcc_new();
    tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
    tcc_compile_string(s, my_program);
    tcc_add_symbol(s, "add", add);

    mem = malloc(tcc_relocate(s, NULL));
    tcc_relocate(s, mem);

    foobar_func = tcc_get_symbol(s, "foobar");

    tcc_delete(s);

    printf("foobar returned: %d\n", foobar_func(32));

    free(mem);
    return 0;
}

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

于 2013-08-22T22:28:18.793 回答
4

本质上,您需要在程序中编写 C++ 编译器(这不是一项简单的任务),并执行与 JIT 编译器相同的操作来运行代码。你实际上已经完成了这一段的 90%:

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

正是——这些程序带有解释器。你通过说 --python 是编译的 C 代码来运行一个 python 程序,python MyProgram.py它能够即时解释和运行你的程序。您需要按照这些方式做一些事情,但要使用 C++ 编译器。

如果您非常需要动态函数请使用不同的语言:)

于 2012-06-13T13:44:29.267 回答
3

一种典型的方法是将 C++(或任何它所写的)项目与脚本语言结合起来。
Lua是最受欢迎的应用程序之一,因为它有据可查、体积小,并且绑定了许多语言。

但是,如果您不朝那个方向研究,也许您可​​以考虑使用动态库?

于 2012-06-13T13:46:00.263 回答
1

是的——你可以用 C++ 为 C++ 编写一个编译器,带有一些额外的特性——编写你自己的函数,自动编译和运行(或不自动)......

于 2012-06-13T13:41:11.020 回答
1

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

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

于 2012-06-13T14:44:25.300 回答
1

您可以查看Runtime Compiled C++(或查看RCC++ 博客和视频),或者尝试其中的一种替代方案

于 2015-02-13T10:01:55.440 回答
1

使用操作码扩展Jay 的答案,以下适用于 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*x汇编中的函数是mulsd 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    
      
于 2021-02-27T13:55:24.500 回答
0

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

于 2012-06-13T13:42:32.853 回答
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;
}
于 2018-11-02T08:47:15.940 回答