有没有办法在运行时用 C 或 C++ 编译其他代码
Is there any way to compile additional code at runtime in C or C++?
这是我想要做的:
- 运行程序并初始化一些数据结构。
- 然后编译可以访问/修改现有数据结构的其他代码。
- 根据需要重复步骤 2。
我希望能够在类Unix系统(特别是Linux和Mac OS X(上使用gcc
(最终Java
(使用C
和C++
来做到这一点。这个想法基本上是为这些语言实现一个读取-评估-打印循环,该循环在输入表达式和语句时对其进行编译,并使用它们来修改现有的数据结构(在脚本语言中一直在这样做(。我正在用 python
编写此工具,它会生成 C
/C++
文件,但这应该无关紧要。
我已经探索过使用共享库执行此操作,但了解到修改共享库不会影响已经在运行的程序。我也尝试使用共享内存,但找不到将函数加载到堆上的方法。我也考虑过使用汇编代码,但尚未尝试这样做。
我宁愿不使用除gcc
以外的任何编译器,除非在gcc
中绝对没有办法做到这一点。
如果有人有任何想法或知道如何做到这一点,任何帮助将不胜感激。
有一个简单的解决方案:
- 创建自己的具有特殊功能的库
- 加载创建的库
- 执行该库中的函数,将结构作为函数变量传递
要使用您的结构,您必须包含与主机应用程序中相同的头文件。
结构体.h:
struct S {
int a,b;
};
主.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
并使用共享内存节省资源。
我认为您可以使用动态库并在运行时加载它们(使用 dlopen
和朋友(来完成此操作。
void * lib = dlopen("mynewcode.so", RTLD_LAZY);
if(lib) {
void (*fn)(void) = dlsym(lib, "libfunc");
if(fn) fn();
dlclose(lib);
}
显然,您必须在进行过程中编译新代码,但是如果您继续替换mynewcode.so
我认为这将对您有用。
尽管LLVM现在主要用于其在编译中的优化和后端角色,但它的核心是低级虚拟机。
LLVM 可以 JIT 代码,即使返回类型可能非常不透明,所以如果你准备好围绕它包装自己的代码并且不太担心将要发生的强制转换,它可能会帮助你。
然而,C和C++对这种事情并不友好。
- 您可以使用运行时编译C++(或查看 RCC++ 博客和视频(或其替代方案之一来执行此操作。
这可以通过OpenCL移植完成
。OpenCL 是一种广泛支持的标准,主要用于将计算卸载到专用硬件,例如 GPU。但是,它在CPU上也可以正常工作,并且实际上执行类似C99的代码的运行时编译作为其核心功能之一(这就是实现硬件可移植性的方式(。较新的版本 (2.1+( 也接受 C++14 的大子集。
这种运行时编译和执行的一个基本示例可能如下所示:
#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
}
如果没有其他方法有效 - 特别是,如果卸载共享库最终在您的运行时平台上不受支持,您可以采取艰难的方式完成。
1(使用system((或其他任何东西来执行gcc或make或其他任何东西来构建代码
2(要么将其链接为平面二进制文件,要么自己解析链接器在您的平台上输出的任何格式(elf?
3(通过mmap((的可执行文件或执行具有执行位集的匿名mmap并在那里复制/解压缩代码(并非所有平台都关心该位,但让我们假设您有一个这样做(
4( 刷新任何数据和指令缓存(因为通常不能保证两者之间的一致性(
5(通过函数指针或其他任何东西调用它
当然还有另一种选择 - 根据您需要的交互级别,您可以构建一个单独的程序并启动它并等待结果,或者分叉并启动它并通过管道或套接字与之通信。 如果这能满足您的需求,那就不那么棘手了。