我如何在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 h5> lua h5> 想不出一个实现这个的方法。也许这可以用哈希表以某种方式实现?或者函数指针?

我可以相当自信地断言,没有办法在严格遵守的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。

有很多方法可以以符合标准的方式做类似的事情,比如将捕获的数据存储在一个结构体中,并将其传递给另一个函数,但就透明地使用函数而言,我认为这是你能做的最好的。


那么还有其他方法实现它吗?

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

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

#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;
}

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

最新更新