能调用任意函数的函数

  C , ASM , 宏函数

其实这是某社团第三次 C 语言作业中一道附加题,当时一看到题目就觉得很有趣,花了一个下午研究了函数调用栈,内嵌汇编的知识,当晚给做了出来。能跑,但是没有解决函数的返回类型这个问题。

这几天接触宏函数接触得比较多,尝试着实现了一个没用的伪泛型宏函数:

#define declare_function(type, function_name, ...) \
type function_name ## _ ## type(__VA_ARGS__)

declare_function(int, my_f, int argc, char *argv[]) {
return call(...);
}

上边这段代码会被展开成为:

int my_f_int(int argc, char *argv[]) {
return call(...);
}

原本按照我的设想,通过临时定义一个函数就可以让用户指定返回类型了,虽然多次调用会使函数重名,但至少离目标更近了一步。但是等到 gcc 报错我才意识到,函数不能嵌套定义…

所以又停滞了好几天,直到我看到一个用 gcc 特性实现的 lambda 宏函数,这件事才终于得到解决。虽然并非标准 C,但在这过程中已经学到够多了,心满意足。

Talk is cheap, here is my code :)

#include <stdio.h>

#define lambda(type, function_body) \
({ type fn function_body fn; })
// usage: lambda(int, (int a, int b) { return a+b; })(2, 3) == 5
// 涉及的第一个特性叫 Statement Expression [1]
// 由括号包裹起来的代码可以有自己的循环、分支甚至是局部变量
// 考虑到这里涉及的代码不止一行,所以用花括号包裹起来(这一项是标准 C)
// 最后一行代码将成为整个 Statement Expression 的值
// 展开得到:
// ({
// int fn (int a, int b) { return a+b; }
// fn;
// }) (2, 3);
// 首先在 Statement Expression 中定义了一个局部函数,然后返回这个函数并调用
// 涉及到第二个特性叫 Nested Functions,可以在函数内定义函数 [2]

#define call(type, f, ...) \ // 变参宏,C99
lambda(type, (void(*pf)(), ...) { \ // 传递的参数无需做处理,告诉编译器需要变参支持即可
int return_to; \ // 储存调用点下一行指令的地址
__asm__ __volatile__ ( \ // 内嵌汇编
\ // 此时栈已经被清空重设,需要恢复未调用时的状态
"movl %%ebp, %%esp;" \ // 恢复栈顶
"movl (%%esp), %%ebp;" \ // 恢复栈基
"movl 4(%%esp), %0;" \ // 保存回溯点
"movl %0, 8(%%esp);" \ // 回溯点往上挪,覆盖掉传进来的第一个参数(pf)
"addl $8, %%esp;" \ // 使栈顶所指符合stdcall约定
\ // 栈状态已经伪装完毕,仿佛未曾来过此地
\ // 覆盖掉了 pf 这个额外的参数,其他参数原样扔给被调用函数
"jmp *%1;" \ // 正式跳到被调用函数
: "=&r" (return_to) \
: "nr" (*pf)); \
})((void(*)()) f, __VA_ARGS__)

int sum(int x, int y) {
return x+y;
}

int main() {
printf("Returned: %d\n", call(int, sum, 1, 2));
}

P.S. 有个简单粗暴的完美解决方法:

#define call(function_name, ...) function_name(__VA_ARGS__)

不过看起来总觉得不够 Geek,是吧…


Reference:

  1. Statement Expression, GCC Manual
  2. Nested Function, GCC Manual
  3. Macros with a Variable Number of Arguments, GCC Manual