我如何在 C 中实现闭包?

How can I implement closures in C?

我想在c中实现以下功能。我写了三种不同编程语言的功能实现:

Python
def get_add(x):
    def add(y):
        return x + y
    return add

add5 = get_add(5)
add5(10) # 15
add10 = get_add(10)
add10(10) # 20
JS
function get_add(x) {
    return (y)=>{
        return x + y
    }
}

add5 = get_add(5)
add5(10) // 15
add10 = get_add(10)
add10(10) // 20
lua
function get_add(num)
    return function(a) return a+num end
end

add5 = get_add(5)
add5(10) -- 15
add10 = get_add(10)
add10(10) -- 20

我想不出实现它的方法。也许这可以以某种方式使用 hash table 来实现?还是函数指针?

我非常相信在严格符合 C 的情况下无法移植地执行此操作的说法。也就是说,如果您愿意对您使用的特定实现的工作方式做出一些慷慨的假设,您可以通过为新代码(或至少是闭包将捕获的数据)分配内存,将其标记为可执行文件,然后进行一些违反标准的指针转换来做到这一点。至少可以在我的机器 (x86-64 Linux) 上运行的示例是:

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <sys/mman.h>

uint8_t (*add(uint8_t x))(uint8_t) {
    // lea eax, [rdi + x]
    // ret
    char code[] = { 0x8D, 0x47, x, 0xC3 };
    char *p = mmap(0, sizeof code, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS,
                   -1, 0);
    memcpy(p, code, sizeof code);
    return (uint8_t(*)(uint8_t))p;
}

int main(void) {
    uint8_t (*add5)(uint8_t) = add(5);
    printf(" 5 + 10 = %" PRIu8 "\n", add5(10));
    printf("10 + 10 = %" PRIu8 "\n", add(10)(10));
    return 0;
}

但如前所述,这充其量是不可移植的,并且绝对不接近惯用的 C。

有一些方法可以以符合标准的方式做与此等效的事情,比如将您要捕获的数据存储在结构中并将其传递给不同的函数,但就透明地使用函数而言,我认为这是你能做的最好的事情了。


So is there any other way to implement it?

是的,但是如果没有外部依赖,它可能会开始变得有点笨拙。

一个选项是这样的,你为每个不同的函数创建一个结构,其中包含你想要捕获的所有变量并将其作为参数传递:

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

struct add {
    uint8_t x;
};

struct add get_add(uint8_t x) {
    return (struct add) {
        .x = x,
    };
}

uint8_t add(struct add info, uint8_t y) {
    return info.x + y;
}

int main(void) {
    struct add add5 = get_add(5);
    printf(" 5 + 10 = %" PRIu8 "\n", add(add5, 10));
    printf("10 + 10 = %" PRIu8 "\n", add(get_add(10), 10));
    return 0;
}

如果您想要一个具有多个柯里化参数的函数,这会变得有点冗长。


@SteveSummit 在评论中关于libffi 的建议也不错。使用它们的闭包 API,这是您可能想要的示例:

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <ffi.h>

void _add(ffi_cif *cif, void *ret, void *args[], void *x) {
    *(ffi_arg*)ret = *(uint64_t*)x + *(uint64_t*)args[0];
}

int main(void) {
    ffi_cif cif;
    ffi_type *args[1];
    void *add_code;
    ffi_closure *add_closure = ffi_closure_alloc(sizeof *add_closure, &add_code);
    uint64_t x = 5;

    if (add_closure) {
        args[0] = &ffi_type_uint64;
        if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_uint64, args) == FFI_OK) {
            if (ffi_prep_closure_loc(add_closure, &cif, _add, &x, add_code) == FFI_OK) {
                printf(" 5 + 10 = %" PRIu64 "\n", ((uint64_t (*)(uint64_t))add_code)(10));
                printf(" 5 + 15 = %" PRIu64 "\n", ((uint64_t (*)(uint64_t))add_code)(15));
            }
        }
    }

    ffi_closure_free(add_closure);

    return 0;
}

我没有检查过,但我猜他们的实现可能只是我给出的第一个示例的更健壮的包装(具有额外的平台支持)。