如何动态加载经常重新生成的c代码?

Phi*_*hil 9 c linux

我希望能够动态生成C代码并将其快速重新加载到我正在运行的C程序中.

我在Linux上,怎么可能这样做?

可以在运行时重新编译并重新加载Linux上的库.so文件吗?

可以在不生成.so文件的情况下进行编译,编译后的输出可能会以某种方式进入内存然后重新加载吗?我想快速重新加载编译的代码.

Bas*_*tch 13

你想要做的是合理的,我正在用MELT(一种用于扩展GCC的高级域特定语言; MELT编译为C,通过用MELT编写的翻译器)编写.

首先,在生成C代码(或许多其他源语言)时,一个好的建议是在内存中保留某种抽象语法树(AST).因此,首先构建生成的C代码的整个AST,然后将其作为C语法发出.如果没有明确的AST,就不要想到你的代码生成框架(换句话说,生成带有一堆printf的C代码是一个维护噩梦,你想要有一些中间表示).

其次,生成C代码的主要原因是利用优秀的优化编译器(另一个原因是C的可移植性和普遍性).如果你不关心生成代码的性能(并且TCC很快将C编译成非常简单和慢速的机器代码),你可以使用其他一些方法,例如使用一些JIT库,如Gnu lightning(非常快速生成慢速机器)代码),Gnu LibjitASMJIT(生成的机器代码更好一点),LLVMGCCJIT(生成良好的机器代码,但生成时间可与编译器相媲美).

因此,如果您生成C代码并希望它快速运行,则C代码的编译时间不可忽略(因为您可能会分叉gcc -O -fPIC -shared 命令以foo.so 生成一些共享对象foo.c).根据经验,生成C代码比编译C代码花费的时间少得多gcc -O.在MELT中,C代码的生成速度比GCC编译速度快10倍(通常快30倍).但是C编译器所做的优化是值得的.

一旦发出C代码,将其编译分成.so共享对象,就可以dlopen了.不要害羞,我的manydl.c示例表明,在Linux上你可以删除大量的共享对象(数十万).真正的瓶颈是生成的C代码的编译.在实践中,您并不需要dlclose在Linux上(除非您编写需要运行数月的服务器程序); 一个未使用的共享模块可以保持几乎没有dlopen,你大部分都在泄漏进程地址空间(这是一个廉价的资源),因为大部分未使用的.so将被换出.dlopen很快完成,需要时间的是C源代码的编译,因为你真的希望优化由C编译器完成.

您可以使用许多其他不同的方法,例如使用字节码解释器并为该字节码生成,使用Common Lisp(例如Linux上的SBCL,它可以动态编译为机器代码),LuaJit,Java,MetaOcaml等.

正如其他人所建议的那样,你并不关心编写C文件的时间,它在实践中将保留在文件系统缓存中(另见本文).写它比编写它要快得多,所以留在内存中并不值得.如果您担心I/O时间,请使用一些tmpfs.

附加物

您询问

可以在运行时.so重新编译和重新加载Linux上的库文件吗?

当然是的:你应该从生成的C代码中派生一个命令来构建库(例如a gcc -O -fPIC -shared generated.c -o generated.so,但是你可以通过运行a来间接地执行它make -j,特别是如果generated.so它足够大以使其与generated.c生成的几个C中的分裂相关文件!)然后你用dlopen动态加载你的库(给它一个完整的路径/some/file/path/to/generated.so,可能还有RTLD_NOW标志),你必须用它dlsym来查找里面的相关符号.不要想到重新加载(第二次)相同generated.so,更好地发出一个唯一的generated1.c(然后generated2.c等...)C文件,然后将其编译为唯一 generated1.so(第二次等generated2.so,等等)然后到dlopen它(这是可以做到的时候许多十万).您可能希望在发出的generated*.c文件中具有一些构造函数,这些函数将在执行dlopen时执行generated*.so

你的基础应用程序应该定义一个关于dlsym -ed名称集(通常是函数)以及如何调用它们的约定.它应该只直接调用你的generated*.sothru dlsym-ed函数指针中的函数.在实践中,您将决定例如每个generated*.c定义一个函数void dynfoo(int),int dynbar(int,int)并使用dlsym"dynfoo","dynbar"并通过函数指针调用这些函数(返回dlsym).您还应该定义如何以及何时调用这些dynfoodynbar将被调用的约定.您可以更好地链接您的基本应用程序,-rdynamic以便您的generated*.c文件可以调用您的应用程序功能.

希望你generated*.so重新定义 现有的名称.例如,您不希望重新定义malloc您的generated*.c并期望所有堆分配函数神奇地使用您的新变体(这可能不起作用,即使它确实如此,也会很危险).

dlclose除了在应用程序清理和退出时间之外,你可能不会费心去动态加载共享对象(但我根本不打扰dlclose).如果你做了dlclose一些动态加载的generated*.so文件,请确保它没有使用任何内容:没有指针,甚至没有返回调用帧中的地址.

PS MELT翻译器目前将57KLOC的MELT代码翻译成近1770KLOC的C代码.