当前位置: 首页 > 面试题库 >

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

周兴朝
2023-03-14
问题内容

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

我在Linux上,怎么做?

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

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


问题答案:

您想做的事情是合理的,我正在用MELT(用于扩展GCC的高级领域特定语言;通过使用MELT编写的翻译器本身将MELT编译为C)来做到这一点。

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

其次,生成C代码的主要原因是要利用良好的优化编译器(另一个原因是C的可移植性和普遍性)。如果您不关心所生成代码的性能(TCC将C很快地编译为非常幼稚和慢速的机器代码),则可以使用其他方法,例如,使用某些JIT库,例如Gnu
lightning
(非常快地生成慢速机器)代码),Gnu
Libjit
或ASMJIT(生成的机器代码要好一点),LLVM或GCCJIT(生成的机器代码好,但生成时间可与编译器相比)。

因此,如果您生成C代码并希望其快速运行,那么C代码的编译时间就可以忽略不计(因为您可能会派生一个gcc -O -fPIC -shared
命令来foo.so 从您生成的代码中创建一些共享库foo.c)。根据经验,生成C代码比编译它(用gcc -O)花费的时间少得多。在MELT中,C代码的生成速度比GCC的编译速度快10倍以上(通常快30倍)。但是由C编译器完成的优化是值得的。

发出C代码,将其编译分支为.so共享库后,就dlopen可以使用。别害羞,我的manydl.c示例演示了在Linux上您可以dlopen大量共享对象(数十万个)。真正的瓶颈是生成的C代码的编译。实际上,您实际上并不需要dlclose在Linux上运行(除非您正在编写一个需要运行数月的服务器程序)。一个未使用的共享模块实际上可以保留dlopen-ed,并且您大多数时候正在泄漏进程地址空间(这是一种廉价的资源),因为大部分未使用的共享模块都.so将被交换掉。dlopen因为要快速完成,所以花费时间是C源代码的编译,因为您确实希望优化由C编译器完成。

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

正如其他人的建议,你不要在乎写一个C文件的时候,它会停留在文件系统缓存在实践中(另见本)。而且编写它比编译它要快得多,因此保留在内存中不值得麻烦。如果您担心I
/ O时间,请使用一些 tmpfs

附加物

您问

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

当然可以:您应该派生一个命令,从生成的C代码构建库(例如,a gcc -O -fPIC -shared generated.c -o generated.so,但是您可以间接地做到这一点,例如,通过运行a make -j,特别是如果a
generated.so足够大以使其可以将generated.c生成的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文件中包含一些构造函数,这些构造函数将dlopengenerated*.so

您的基本应用程序应该已经定义了有关dlsym
-ed名称集(通常是函数)以及如何调用它们的约定。它仅应直接在generated*.so通过dlsym函数的指针中调用函数。在实践中,你将决定例如,每个generated*.c定义一个函数void dynfoo(int),并int dynbar(int,int)和使用dlsym"dynfoo""dynbar"和调用这些通函数指针(由返回dlsym)。你也应该定义如何以及何时这些公约dynfoo,并dynbar会被调用。您最好将基本应用程序与链接,-rdynamic以便generated*.c文件可以调用应用程序函数。

希望你generated*.so重新定义 现有的
名称。例如,您不想重新定义malloc自己,generated*.c并希望所有堆分配函数都神奇地使用您的新变体(这可能不起作用,即使这样做也很危险)。

dlclose除了在应用程序清理和退出时,您可能不会理会动态加载的共享对象(但我完全不会理会dlclose)。如果您要dlclose动态加载generated*.so文件,请确保其中没有使用任何文件:没有指针,甚至没有调用帧中的返回地址。

PS MELT转换器当前将MELT代码的57KLOC转换为C语言的将近1770KLOC。



 类似资料:
  • 我从“dynconfigflows”-目录中加载xml配置文件,然后动态创建IntegrationFlow。 每个xml文件一个IntegrationFlow。如果配置文件已编辑,则应更新IntegrationFlow。IntegrationFlow的注册id被设置为配置文件名。 ,因此当前的流应该平稳地停止(没有更多的输入消息和当前处理的消息应该完成)。在此之后,应该删除它,并注册流的更新版本。

  • 我正在使用Ansible来设置EC2实例并部署一个应用程序。有一个hosts脚本,它收集标签、相关服务器和分组信息。我想把这些动作作为一个剧本来运行,所以 如果需要,将创建新实例 主机脚本加载清单(包括服务器的事实) 部署剧本有效 但我还没找到一个命令让它翻倍。

  • 我正在使用Jooq和GradleJooq插件生成代码。它工作得很好,但在添加表或删除列时,我在更新生成的代码时遇到了一个问题。我可以通过更改“packageName”配置参数来强制更新,并构建一个新的包。通过返回原始名称,代码按预期进行了更新。 在使用我的设置更改模式后,重新生成代码的正确方法是什么? 我正在使用https://github.com/etiennestuder/gradle-joo

  • 我正在使用Angular UI路由器,希望重新加载当前状态并刷新所有数据/重新运行当前状态及其父级的控制器。 我有3个州级:directory.organisations.details 目录组织包含一个包含组织列表的表。单击表loads目录中的项目。组织。$StateParams传递项目ID的详细信息。因此,在详细信息状态下,我加载此项目的详细信息,编辑它们,然后保存数据。目前一切正常。 现在我

  • 问题内容: 我正在使用Angular UI Router,并希望重新加载当前状态并刷新所有数据/为当前状态及其父级重新运行控制器。 我有3个状态级别: directory.organisations.details directory.organisations 包含一个带有组织列表的表。单击表中的一个项目将加载$ directoryParams传递该项目的ID的 directory.organi

  • 本文向大家介绍C++如何动态的生成对象详解,包括了C++如何动态的生成对象详解的使用技巧和注意事项,需要的朋友参考一下 前言 可能说起C++大多数人都觉着难学,其实我也是这么觉着的,在这个移动端火到爆的时代,我都想改行了,移动端做东西那都是现有的第三方库,拿来就可以用,而且稳定性好,开发速度快,而且最关键的是出东西。再谈一谈动态生成对象,为什么强大的C++不支持呢?想用这样功能的人都必须自己实现一