能调用任意函数的函数

其实这是某社团第三次 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: %dn", 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

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据