我如何在 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;
}
我没有检查过,但我猜他们的实现可能只是我给出的第一个示例的更健壮的包装(具有额外的平台支持)。
我想在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;
}
我没有检查过,但我猜他们的实现可能只是我给出的第一个示例的更健壮的包装(具有额外的平台支持)。