你想做的事情是合理的,而我正是这样做的MELT http://gcc-melt.org/(一种扩展 GCC 的高级领域特定语言;MELT 通过用 MELT 编写的翻译器本身编译为 C)。
首先,在生成 C 代码(或许多其他源语言)时,一个好的建议是保留某种类型的抽象语法树 http://en.wikipedia.org/wiki/Abstract_syntax_tree(AST)在内存中。因此,首先构建生成的 C 代码的整个 AST,然后将其作为 C 语法发出。不要认为您的代码生成框架没有显式 AST(换句话说,使用一堆 printf 生成 C 代码是维护的噩梦,您希望有一些中间表示)。
其次,生成C代码的主要原因是为了利用良好的优化编译器(另一个原因是C的可移植性和普遍性)。如果您不关心生成代码的性能(并且 TCC 很快地将 C 编译成非常幼稚且缓慢的机器代码),您可以使用其他一些方法,例如使用一些 JIT 库,例如Gnu 闪电 http://www.gnu.org/software/lightning/(非常快速地生成缓慢的机器代码),Gnu Libjit http://www.gnu.org/software/dotgnu/libjit-doc/libjit.html or ASMJIT https://github.com/kobalicek/asmjit(生成的机器代码更好一点),LLVM http://llvm.org or GCCJIT https://gcc.gnu.org/onlinedocs/jit/(生成的机器代码很好,但生成时间与编译器相当)。
因此,如果您生成 C 代码并希望它快速运行,则 C 代码的编译时间不可忽略(因为您可能会 forkgcc -O -fPIC -shared
创建一些共享对象的命令foo.so
从你生成的foo.c
)。根据经验,生成 C 代码比编译它花费的时间少得多(使用gcc -O
)。在 MELT 中,C 代码的生成速度比 GCC 编译速度快 10 倍以上(通常快 30 倍)。但 C 编译器所做的优化是值得的。
一旦你发出你的 C 代码,将其编译分叉成.so
共享对象,你可以dlopen
它。别害羞,我的manydl.c http://starynkevitch.net/Basile/manydl.c示例演示了在 Linux 上您可以 dlopen 大量共享对象(数十万个)。真正的瓶颈是生成的 C 代码的编译。在实践中,你真的不需要dlclose
在 Linux 上(除非您正在编写需要运行数月的服务器程序);未使用的共享模块实际上可以保留dlopen
-ed 并且您主要是在泄漏进程地址空间(这是一种廉价的资源),因为其中大部分未使用.so
将被换出。dlopen
完成得很快,需要时间的是 C 源代码的编译,因为你确实希望由 C 编译器来完成优化。
您可以使用许多其他不同的方法,例如有一个字节码解释器并生成该字节码,使用 Common Lisp(例如 Linux 上的 SBCL,它动态编译为机器代码)、LuaJit、Java、MetaOcaml 等。
正如其他人所建议的,您不太关心编写 C 文件的时间,它实际上会保留在文件系统缓存中(另请参阅this https://stackoverflow.com/a/7880788/841108)。而且编写它比编译它要快得多,因此保留在内存中并不值得。使用一些tmpfs如果您担心 I/O 时间。
addenda
你问
可以图书馆吗.so
Linux 上的文件被重新编译并re-运行时加载?
当然是的:您应该派生一个命令来从生成的 C 代码构建库(例如gcc -O -fPIC -shared generated.c -o generated.so
,但你可以间接地做到这一点,例如通过运行make -j
,特别是如果generated.so
足够大以使其与拆分相关generated.c
在几个 C 生成的文件中!)然后你动态加载你的库dlopen http://linux.die.net/man/3/dlopen(给出完整路径,例如/some/file/path/to/generated.so
,并且可能是RTLD_NOW
标志,到它),你必须使用dlsym
找到里面的相关符号。别想re-加载(第二次)相同generated.so
,更好地发出独特的generated1.c
(then generated2.c
等等...)C 文件,然后将其编译为unique generated1.so
(第二次到generated2.so
等...)然后dlopen
它(并且这可以完成数十万次)。您可能希望在发出的generated*.c
文件,一些构造函数 http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html将在以下位置执行的函数dlopen
的时间generated*.so
您的基础应用程序应该定义一个关于一组的约定dlsym http://linux.die.net/man/3/dlsym-ed 名称(通常是函数)以及它们的调用方式。它应该只直接调用你的函数generated*.so
thru dlsym
-ed 函数指针。在实践中,您会决定例如每个generated*.c
定义一个函数void dynfoo(int)
and int dynbar(int,int)
并使用dlsym
with "dynfoo"
and "dynbar"
并通过函数指针调用这些(由dlsym
)。您还应该定义如何以及何时这些的约定dynfoo
and dynbar
会被称为。您最好将您的基本应用程序链接到-rdynamic
这样你的generated*.c
文件可以调用您的应用程序函数。
You don't想要你的generated*.so
to 重新定义 existing名称。例如,您不想重新定义malloc
在你的generated*.c
并期望所有堆分配函数神奇地使用您的新变体(这可能不起作用,即使起作用,也会很危险)。
你可能不会费心去dlclose
动态加载的共享对象,除了在应用程序清理和退出时(但我根本不费心去dlclose
)。如果你这样做dlclose
一些动态加载的generated*.so
文件中,请确保其中未使用任何内容:其中不存在任何指针,甚至不存在调用帧中的返回地址。
附: MELT 翻译器目前已将 57KLOC 的 MELT 代码翻译为近 1770KLOC 的 C 代码。