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

dte*_*ech 54 c++ runtime function dynamic

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

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

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

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

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

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

Wal*_*ter 44

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

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

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

  • ...鉴于编译器安装在目标机器上. (20认同)
  • @MathieuRodic 别担心,我们会将编译器静态链接到我们自己的程序。:) (5认同)

Jay*_*Jay 38

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

例如

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

  • 什么是黑客!你怎么知道任何函数的字节码?这真的有用吗? (22认同)
  • 如何获取函数的字节码?! (4认同)
  • 这根本不起作用。第一个问题是优先级:(()`比`*`绑定更紧密,因此它解析为`*(reinterpret_cast &lt;void **&gt;(&func)())`。失败是因为您不能调用`void **`(这不是函数类型)。修复优先级无济于事:如果先取消引用,它将尝试调用`void *`,后者也不是函数类型。如果您设法正确地将`&func`(数组的地址,大概是:`byte [3] func`实际上是语法错误)转换为函数指针的地址(`void(**)())和取消引用它,它将崩溃,因为... (4认同)
  • ...它现在将数组的内容 (`0x90, 0x0f, 0x1`) 解释为函数指针,这是无稽之谈。如果你想让它编译,你需要类似 `unsigned char func[] = { 0x90, 0x0f, 0x1 }; reinterpret_cast&lt;void (*)()&gt;(func)();`(这些指针到指针的东西都没有),它可能仍会在运行时崩溃,但至少它现在要求 CPU 从一个字节执行代码大批。 (3认同)
  • (这可能会崩溃,因为即使您具有适合处理器体系结构的正确字节码,堆栈和数据段也可能未在任何现代OS上标记为可执行文件。) (3认同)
  • @Jay 我不是在分裂头发。声明语法(最不重要的部分)是`byte func[3]`,而不是`byte[3] func`。如果你写 `&amp;func`,你会得到一个指向整个数组的指针。这仍然是一个单一的间接级别。如果您尝试取消引用它两次(在您的代码中:一次通过`*`,一次通过`()`(函数调用运算符)),您最终会将这3 个字节视为内存地址。即使您忽略了所有优先级和类型错误,这也行不通。 (2认同)

Mil*_*lan 16

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

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


use*_*432 9

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

该示例仅适用于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 */
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

产量

$ 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
Run Code Online (Sandbox Code Playgroud)


Mat*_*dic 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;
}
Run Code Online (Sandbox Code Playgroud)

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


bam*_*s53 5

除了简单地使用嵌入式脚本语言(Lua的是伟大的嵌入)或编写自己的C编译器++在运行时使用,如果你真的想使用C++,你可以只使用现有的编译器.

例如,Clang是一个构建为库的C++编译器,可以很容易地嵌入到另一个程序中.它被设计为从IDE等需要分析和处理C++源以各种方式程序中使用,但使用LLVM编译器infrasructure作为后端它也有在运行时生成代码,交给你一个函数指针,你可以的能力调用以运行生成的代码.


Luc*_*ore 1

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