繁体   English   中英

在C中写`eval()`

[英]Writing `eval()` in C

我一直在尝试在C中制作一个eval函数。

目前,我的想法是使用所有标准库C函数和我所做的所有函数制作一个散列String -> function pointer ,这样我就可以处理函数调用(在已经定义的函数上)。

但是,用字符串定义函数(即调用eval("int fun(){return 1;}") )仍然是个问题,我不知道如何在运行时处理这个问题,有没有人有任何想法?

变量定义似乎不是太大的问题,因为我可以使用另一个散列var_name -> pointer并在需要变量时使用该指针。

顺便说一句,我不关心性能,我想让这个工作。

这是可能的,但是很痛苦。 您需要编写将文本作为输入并生成语法树的解析器; 那么你需要简化构造(例如,将循环转换为goto语句并将表达式简化为只有1次操作的单静态赋值)。 然后,您需要将语法树中的所有模式与执行相同任务的目标计算机上的指令序列进行匹配。 最后,您需要选择用于每条指令的寄存器,必要时将它们溢出到堆栈中。

简而言之,在C中编写eval的实现是可能的,但是大量的工作需要在计算机科学的几个领域中拥有大量的专业知识和知识。 编写编译器的复杂性是解释大多数编程语言或使用具有自定义字节码的虚拟机的确切原因。 像clang和llvm这样的工具使这更容易,但是那些是用C ++编写的,而不是C.

试图解析C是背后真正的痛苦; 但我们已经知道如何解析C; 调用C编译器! 在这里,我们将eval代码编译成动态库并加载它。

您可能会遇到动态代码无法在您自己的代码中找到其他函数或变量的问题; 简单的解决方案是编译整个程序,除了main()作为库,并将动态代码库链接到它。 您可以通过将库加载地址设置为比主加载地址高几K来避免-fpic惩罚。 在Linux上,如果没有剥离,可执行文件可以解析库中未解析的符号,glibc依赖于此功能; 但是,编译器优化有时会妨碍,因此总库方法可能是必要的。

以下示例代码适用于Linux。 这可以用于其他Unix,包括Mac OSX,只需要很少的工作。 尝试使用Windows是可能的,但更难的是因为你不能保证C编译器,除非你愿意发货; 并且在Windows上有关于多个C运行时的令人讨厌的规则,因此您必须使用您发布的相同编译器进行构建,因此还必须使用您发布的相同编译器进行构建。 此外,您必须使用此处的总库技术或主程序中的符号才会在库中解析(PE文件格式无法表达必要的)。

此示例代码无法使eval()代码保存状态; 如果你需要这个,你应该通过主程序中的变量或(首选)按地址传递状态结构。

如果您尝试在嵌入式环境中执行此操作,请不要这样做。 这在嵌入式世界中是个坏主意。

回答里奇的评论; 我从未见过eval()块的参数类型和返回类型不是从周围代码中静态确定的情况; 除此之外你怎么能打电话呢? 下面的示例代码可以被删除,提取共享部分,因此每类型部分只有几行; 运动留给读者。

如果你没有特定的理由想要动态C; 尝试使用嵌入式LUA,并使用定义良好的界面。

/* gcc -o dload dload.c -ldl */

#include <dlfcn.h>
#include <stdio.h>

typedef void (*fevalvd)(int arg);

/* We need one of these per function signature */
/* Disclaimer: does not support currying; attempting to return functions -> undefined behavior */
/* The function to be called must be named fctn or this does not work. */
void evalvd(const char *function, int arg)
{
        char buf1[50];
        char buf2[50];
        char buf3[100];
        void *ctr;
        fevalvd fc;
        snprintf(buf1, 50, "/tmp/dl%d.c", getpid());
        snprintf(buf2, 50, "/tmp/libdl%d.so", getpid());
        FILE *f = fopen(buf1, "w");
        if (!f) { fprintf (stderr, "can't open temp file\n"); }
        fprintf(f, "%s", function);
        fclose(f);
        snprintf(buf3, 100, "gcc -shared -fpic -o %s %s", buf2, buf1);
        if (system(buf3)) { unlink(buf1); return ; /* oops */ }

        ctr = dlopen(buf2, RTLD_NOW | RTLD_LOCAL);
        if (!ctr) { fprintf(stderr, "can't open\n"); unlink(buf1); unlink(buf2); return ; }
        fc = (fevalvd)dlsym(ctr, "fctn");
        if (fc) {
                fc(arg);
        } else {
                fprintf(stderr, "Can't find fctn in dynamic code\n");
        }
        dlclose(ctr);
        unlink(buf2);
        unlink(buf1);
}

int main(int argc, char **argv)
{
        evalvd("#include <stdio.h>\nvoid fctn(int a) { printf(\"%d\\n\", a); }\n", 10);
}

几个星期前,我想做类似的事情,这是我偶然发现的第一个问题,因此在这里回答,现在我对此持有一些:)我很惊讶没人提到tcc (特别是libtcc )让你从字符串编译代码并调用如此定义的函数。 例如:

int (*sqr)(int) = NULL;
TCCState *S = tcc_new();

tcc_set_output_type(S, TCC_OUTPUT_MEMORY);
tcc_compile_string(S, "int squarer(int x) { return x*x; }");
tcc_relocate(S, TCC_RELOCATE_AUTO);
sqr = tcc_get_symbol(S, "func");

printf("%d", sqr(2));
tcc_delete(S);

(为简洁起见省略了错误处理)。 除了这个基本的例子,如果想在动态函数中使用宿主程序的变量,还需要做更多的工作。 如果我有一个变量int N; 我想用它,我需要两件事:在代码字符串中:

 ... "extern int N;"

告诉tcc

tcc_add_symbol(S, "N", &N);

同样,有注入宏的API,打开整个库等HTH。

考虑到一些限制,使用OpenCL可能是在C / C ++中实现eval的一种可能方式。 一旦您的OpenCL实现提供编译内核并执行它们的能力,无论在CPU或GPU(或其他某些“加速器”设备)上的位置,这意味着您可以在C / C ++应用程序运行时生成内核代码字符串,编译它们并入队执行。 此外,OpenCL API还提供查找内核编译,链接和执行错误的功能。 所以,请看一下OpenCL。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM