计算在C中使用哪种函数(或代码类型)



我有一个不寻常的问题。假设我有N个函数:

void function1(){ //some code here }
void function2(){ //some code here }
...
void functionN(){ //some code here }

有没有任何方法,我可以在没有IF语句的情况下动态计算或找出要使用的函数?根据函数名的名称,调用它?让我给你们看伪代码,它可以更好地描述情况:

for(int I=1;I<=N;I++){
functionI();
}

我的意思是,如果有可能计算(例如在char数组中,但也可以通过任何其他方式)某种代码,我稍后将插入并使用这些代码。但不是字符串,而是直接像代码一样。让我举一个不同的例子:

int num=3;
char functionInString[]="printf(num);
//Some code, that would for example change letters in 
functionInString, for example to different fuction 
consisting of letters
//And here, do whatever is written in functionToString

很抱歉我不够清楚。有人能告诉我,它在C语言或任何不同的语言中是否可能,这个概念是如何命名的吗?

您可能需要了解什么是闭包和回调。函数指针很有用,但单独使用可能还不够(阅读更多内容,了解函数指针如何对实现闭包有用)。

你应该学习更多关于C的知识,所以读一些关于C编程的好书。我建议下载C11标准n1570并浏览一下。一个非常重要的概念是未定义的行为,你应该对此感到害怕。

如果有可能计算(例如,在char数组中,也可以通过任何其他方式)某种代码,我稍后将插入并使用这些代码。但不是字符串,而是直接像代码一样。

这在纯标准C代码中是不可能的(因为组成程序的翻译单元集是固定的),原则上任何有效的函数指针值都应该指向某些现有的函数(因此狭义调用任何其他函数指针值将是未定义的行为)。然而,一些实现能够构造(以某种特定于实现的方式),以某种方式"有效"的新函数指针(但这是C11标准之外的)。在纯哈佛体系结构上,代码放在ROM中,这是不可能的。

生成和动态加载插件

然而,如果您在现代操作系统(我推荐Linux)下运行,您可能会使用动态加载和插件功能。我特别关注Linux(对于Windows,细节非常不同,邪恶在于细节;阅读Levine的书链接器和加载程序)。阅读操作系统:三个简单的部分了解更多关于操作系统的信息。

因此,(在Linux上)可以在运行时在一些临时文件中生成一些C代码,例如/tmp/generated.c;您需要定义关于该文件的约定(例如,它定义了一个具有一些附加所需属性的void pluginfun(int)函数);然后你派生一个编译命令,将其编译成一个共享对象(阅读Drepper的How to write shared libraries),这是一个插件。因此,您的程序可能会运行(可能使用system(3),或更低级别的系统调用,如fork(2)、execve(2)和waitpid(2)等)编译过程,如gcc -Wall -O -fPIC /tmp/generated.c -shared -o /tmp/generatedplugin.so;稍后,您的主程序将使用dlopen(3):加载该插件

void* dlh = dlopen("/tmp/generatedplugin.so", RTLD_NOW);
if (!dlh) { 
fprintf(stderr, "dlopen /tmp/generatedplugin.so failed: %sn",
dlerror());
exit(EXIT_FAILURE);
}

由于您关心函数指针,如果您将它们的签名声明为类型:,那么您的代码将更可读

typedef void pluginfun_type(int);

然后,指向该签名函数的函数指针就很容易声明了:

pluginfun_type* fptr = NULL; 

你的程序可以用dlsym(3)获得插件中的pluginfun函数地址:

fptr = (pluginfun_type*) dlsym(dlh, "pluginfun");
if (!fptr) {
fprintf(stderr, "dlsym of pluginfun failed: %sn", dlerror());
exit(EXIT_FAILURE);
}

最后使用CCD_ 5调用该函数。

您应该使用gcc -O -Wall foo.o bar.o -rdynamic -ldl -o foobarprog来链接您的主程序。您可以最后调用dlclose(3),但如果您的插件中的某个函数的调用框架仍然处于活动状态,则不应该这样做。

这种生成插件的方法在Linux上运行得非常好。我的manydl.c是一个生成"随机"c代码的玩具程序,将生成的c代码的编译分叉到生成的插件中,并加载该插件(并且可以重复多次)。它表明,在实践中,Linux程序可以生成和加载数十万(如果你足够耐心的话,甚至可能是数百万)插件。代码段泄漏在实践中是可以接受的(并且可以通过小心使用dlclose来避免)

而且今天的电脑很快。您可能会在每次REPL交互中生成一个小插件(少于1000行C),并对其进行编译和加载(对于这样一个小的插件,它通常需要不到0.1秒的时间),这实际上与人类交互兼容(我在我过时的GCC MELT项目中这样做;我将在bismon-chariot-doc.pdf草案报告中描述的bismon项目中这样这样做)。


使用JIT编译库

要在运行时动态生成代码,还可以使用一些JIT编译库。它们有很多,包括libgccjit、LLVM(在C++中)、asmjit、libjit、GNU闪电、tinycc及其libtcc。其中一些能够快速发出机器代码,但代码的性能可能不是很好。其他人正在进行优化,比如一个好的C编译器(特别是在内部使用GCC的libgccjit)。当然,这些优化需要一些时间,因此机器代码的发出速度要慢得多,但其性能与优化的C代码一样好。


嵌入解释器

在许多情况下,脚本语言提供某种eval。设计了几个解释器,以便可以轻松地嵌入到您的应用程序中(注意事项),尤其是Lua或Guile(以及Nim)。更多的解释器(Ocaml、Python、Perl、Parrot、Ruby…)也可以以某种方式嵌入到您的应用程序中(您可能需要了解垃圾收集术语)。当然,所有这些都需要一些编码约定。而且解释器在实践中比编译代码慢。


术语

有人能告诉我,在C或任何不同的语言中,这是否可能,这个概念是如何命名的吗?

您可能需要阅读更多关于元编程、eval、多级编程、同源性、反射、延续、类型内省、堆栈跟踪的内容(请考虑Ian Taylor的libbacktrace)。

你可能对类似Lisp的语言感兴趣。首先阅读SICP,然后阅读Queinnec的《小片段Lisp》和Scott的《编程语言语用学》。请注意,SBCL(一个公共Lisp实现)在每次REPL交互时都会生成机器代码。

既然你提到了对人工智能的兴趣,你可以看看J.Pitrat的博客。他的CAIA系统是自举的,因此生成了其全部C代码(大约500KLOC)。

尝试使用函数指针数组;

#include <stdlib.h>
#include <stdio.h>
void myfunc1(){printf("1n");};
void myfunc2(){printf("2n");};
void myfunc3(){printf("3n");};
void myfunc4(){printf("4n");};
void myfunc5(){printf("5n");};
void (*myfuncs[5])() = {myfunc1, myfunc2, myfunc3, myfunc4, myfunc5};
int main(int argc, char *argv[])
{
for(int i=0;i<5;i++) {
(myfuncs[i])();
}
exit(EXIT_SUCCESS);
}

使用函数指针数组。首先定义格式:

typedef void func_t (void);

然后创建一个函数指针数组:

func_t* func[n] = {function1, function2, function3};

示例:

#include <stdio.h>
void function1 (void) { puts(__func__); }
void function2 (void) { puts(__func__); }
void function3 (void) { puts(__func__); }
typedef void func_t (void);
#define n 3
int main()
{
func_t* func[n] = {function1, function2, function3};
for(size_t i=0; i<n; i++)
{
func[i]();
}
}

相关内容

  • 没有找到相关文章

最新更新