有没有办法在运行时用C或C ++编译其他代码?

时间:2012-05-12 14:39:51

标签: c++ c linux gcc runtime-compilation

这是我想要做的:

  1. 运行程序并初始化一些数据结构。
  2. 然后编译可以访问/修改现有数据结构的其他代码。
  3. 根据需要重复步骤2.
  4. 我希望能够在类似Unix的系统(尤其是Linux和Mac OS)上使用C(最终C++)同时使用gccJava执行此操作X)。我们的想法是基本上为这些语言实现一个read-eval-print循环,它们在输入时编译表达式和语句,并使用它们来修改现有的数据结构(在脚本语言中一直都是这样)。我在python中编写此工具,该工具会生成C / C++个文件,但这不应该相关。

    我已经探讨了使用共享库执行此操作,但了解到修改共享库不会影响已在运行的程序。我也尝试过使用共享内存但无法找到将函数加载到堆上的方法。我还考虑过使用汇编代码但尚未尝试这样做。

    我宁愿不使用gcc以外的任何编译器,除非在gcc中完全没办法。

    如果有人有任何想法或知道如何做到这一点,我们将不胜感激。

6 个答案:

答案 0 :(得分:12)

有一个简单的解决方案:

  1. 创建具有特殊功能的自己的库
  2. 加载创建的库
  3. 从该库执行函数,将结构作为函数变量传递
  4. 要使用您的结构,您必须包含相同的头文件,如主机应用程序。

    <强> structs.h:

    struct S {
        int a,b;
    };
    

    <强> main.cpp中:

    #include <iostream>
    #include <fstream>
    #include <dlfcn.h>
    #include <stdlib.h>
    
    #include "structs.h"
    
    using namespace std;
    
    int main ( int argc, char **argv ) {
    
        // create own program
        ofstream f ( "tmp.cpp" );
        f << "#include<stdlib.h>\n#include \"structs.h\"\n extern \"C\" void F(S &s) { s.a += s.a; s.b *= s.b; }\n";
        f.close();
    
        // create library
        system ( "/usr/bin/gcc -shared tmp.cpp -o libtmp.so" );
    
        // load library        
        void * fLib = dlopen ( "./libtmp.so", RTLD_LAZY );
        if ( !fLib ) {
            cerr << "Cannot open library: " << dlerror() << '\n';
        }
    
        if ( fLib ) {
            int ( *fn ) ( S & ) = dlsym ( fLib, "F" );
    
            if ( fn ) {
                for(int i=0;i<11;i++) {
                    S s;
                    s.a = i;
                    s.b = i;
    
                    // use function
                    fn(s);
                    cout << s.a << " " << s.b << endl;
                }
            }
            dlclose ( fLib );
        }
    
        return 0;
    }
    

    <强>输出:

    0 0
    2 1
    4 4
    6 9
    8 16
    10 25
    12 36
    14 49
    16 64
    18 81
    20 100
    

    您还可以创建可变程序,它将自行更改(源代码),重新编译自己,然后用 execv 替换它的实际执行并节省资源共享内存。

答案 1 :(得分:11)

我认为您可以使用动态库并在运行时加载它们(使用dlopen和朋友)来完成此任务。

void * lib = dlopen("mynewcode.so", RTLD_LAZY);
if(lib) {
    void (*fn)(void) = dlsym(lib, "libfunc");

    if(fn) fn();
    dlclose(lib);
}

您显然必须在编写新代码时进行编译,但如果您继续替换mynewcode.so,我认为这对您有用。

答案 2 :(得分:4)

尽管LLVM现在主要用于编译中的优化和后端角色,但它的核心是低级虚拟机。

LLVM可以JIT代码,即使返回类型可能非常不透明,所以如果您准备好自己包装自己的代码并且不太担心将要发生的转换,它可能会有所帮助你。

然而,C和C ++对这类事情并不友好。

答案 3 :(得分:3)

是的 - 您可以使用Runtime Compiled C++(或查看RCC++ blog and videos)或alternatives之一来执行此操作。

答案 4 :(得分:3)

这可以通过OpenCL

移植完成

OpenCLwidely supported标准,主要用于将计算卸载到专用硬件(如GPU)。但是,它在CPU上工作得很好,并且实际上执行类似C99的代码的运行时编译作为其核心功能之一(这就是如何实现硬件可移植性)。较新的版本(2.1+)也接受C ++ 14的大部分。

此类运行时编译的基本示例&amp;执行可能看起来像这样:

#ifdef __APPLE__
#include<OpenCL/opencl.h>
#else
#include<CL/cl.h>
#endif
#include<stdlib.h>
int main(int argc,char**argv){//assumes source code strings are in argv
    cl_int e = 0;//error status indicator
    cl_platform_id platform = 0;
    cl_device_id device = 0;
    e=clGetPlatformIDs(1,&platform,0);                                      if(e)exit(e);
    e=clGetDeviceIDs(platform,CL_DEVICE_TYPE_ALL,1,&device,0);              if(e)exit(e);
    cl_context context = clCreateContext(0,1,&device,0,0,&e);               if(e)exit(e);
    cl_command_queue queue = clCreateCommandQueue(context,device,0,&e);     if(e)exit(e);
    //the lines below could be done in a loop, assuming you release each program & kernel
    cl_program program = clCreateProgramWithSource(context,argc,(const char**)argv,0,&e);
    cl_kernel kernel = 0;                                                   if(e)exit(e);
    e=clBuildProgram(program,1,&device,0,0,0);                              if(e)exit(e);
    e=clCreateKernelsInProgram(program,1,&kernel,0);                        if(e)exit(e);
    e=clSetKernelArg(kernel,0,sizeof(int),&argc);                           if(e)exit(e);
    e=clEnqueueTask(queue,kernel,0,0,0);                                    if(e)exit(e);
    //realistically, you'd also need some buffer operations around here to do useful work
}

答案 5 :(得分:2)

如果没有其他工作 - 特别是,如果卸载共享库最终在您的运行时平台上不受支持,那么您可以这么做。

1)使用system()或其他任何来执行gcc或make或其他任何来构建代码

2)要么将其链接为平面二进制文件,要么解析链接器自己在平台上输出的任何格式(elf?)

3)通过mmap()执行可执行文件获取一些可执行页面,或者使用执行位设置执行匿名mmap并在那里复制/解压缩代码(并非所有平台都关心这一点,但让我们假设你有一个确实如此)

4)刷新任何数据和指令缓存(因为通常不保证两者之间的一致性)

5)通过函数指针或其他任何

调用它

当然还有另一种选择 - 根据您需要的交互级别,您可以构建一个单独的程序,然后启动它并等待结果,或者分叉并启动它并通过管道或套接字与它通信。如果这可以满足您的需求,那就不那么棘手了。