vlambda博客
学习文章列表

在C语言里, 如何通过输入函数名字来调用函数?

在C语言里, 如何通过输入函数名字来调用函数?

旧文重发。


大致有三种方法:

函数字典

缺点是代码耦合在一起, 无法复用。

#include <iostream>#include <map>#include <string>#include <functional>
void foo() { std::cout << "foo()"; }void boo() { std::cout << "boo()"; }void too() { std::cout << "too()"; }void goo() { std::cout << "goo()"; }
int main() { std::map<std::string, std::function<void()>> functions; functions["foo"] = foo; functions["boo"] = boo; functions["too"] = too; functions["goo"] = goo;
std::string func; std::cin >> func; if (functions.find(func) != functions.end()) { functions[func](); } return 0;}

编译阶段将符号信息输出到打哪库

利用nm或者objdump, 在Makefile中在编译阶段将符号信息输出到源代码里. 缺点是每次在不同的环境里运行都要重新编译一次。

objs = main.o reflect.o
main: $(objs) gcc -o $@ $^ nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c gcc -c .reflect.real.c -o .reflect.real.o gcc -o $@ $^ .reflect.real.o nm $@ | awk 'BEGIN{ print "#include <stdio.h>"; print "#include \"reflect.h\""; print "struct sym_table_t gbl_sym_table[]={" } { if(NF==3){print "{\"" $$3 "\", (void*)0x" $$1 "},"}} END{print "{NULL,NULL} };"}' > .reflect.real.c gcc -c .reflect.real.c -o .reflect.real.o gcc -o $@ $^ .reflect.real.o

以上方法都可以在Stackoverflow上找到[1]

而我要给出的是另一种方法:

ELF查符号表, 找出函数的名字与值

背景知识可先看看:



ELF 是什么?

ELF(Executable and Linking Format) 是一个开放标准,各种 UNIX 系统的可执行文件都采用 ELF 格式,它有四种不同的类型:

可重定位的目标文件 (Relocatable, 或者 Object File), Linux 的.o, Windows 的.obj可执行文件 (Executable), Linux 的.out, Windows 的.exe共享库 (Shared Object, 或者 Shared Library), Linux 的.so, Windows 的.DLL核心转储文件 (Core Dump File) a, Linux 下的 core dump

方法大致是, 读取编译后的程序(可执行文件也是ELF), 找到 SHT_SYMTAB(符号表), 然后遍历符号表, 找到与函数名一样的符号.

因为现在的C语言已经不会在符号前加上下划线了, 所以可以名字与符号名相同.

符号是什么?

void (*fun)(void) = (void*)sym.st_value;(*fun)();

所有 extern 函数的符号都会存在可执行文件中, 所以即便是多个模块的编译链接, 这个函数依然适用.

遍历符号表

遍历符号表的代码如下:

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <libelf.h>#include <gelf.h>
voidmain(int argc, char **argv){ Elf *elf; Elf_Scn *scn = NULL; GElf_Shdr shdr; Elf_Data *data; int fd, ii, count;
elf_version(EV_CURRENT);
fd = open(argv[1], O_RDONLY); elf = elf_begin(fd, ELF_C_READ, NULL);
while ((scn = elf_nextscn(elf, scn)) != NULL) { gelf_getshdr(scn, &shdr); if (shdr.sh_type == SHT_SYMTAB) { /* found a symbol table, go print it. */ break; } }
data = elf_getdata(scn, NULL); count = shdr.sh_size / shdr.sh_entsize;
/* print the symbol names */ for (ii = 0; ii < count; ++ii) { GElf_Sym sym; gelf_getsym(data, ii, &sym); printf("%s\n", elf_strptr(elf, shdr.sh_link, sym.st_name)); } elf_end(elf); close(fd);}

只需在找到符号的代码里,加上:

 if(!strcmp(sym_name, function_name)) { void (*fun)(void) = (void*)sym.st_value; (*fun)(); }

References