Created
December 18, 2009 12:10
-
-
Save vidarh/259462 to your computer and use it in GitHub Desktop.
A number of ways to implement closures in C, in preparation of an upcoming blog post
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
struct closure { | |
void (* call)(struct closure *); | |
int x; | |
}; | |
void block(struct closure * env) { | |
env->x += 1; | |
printf ("block: x is %d\n", env->x); | |
} | |
struct closure * foo(int x) | |
{ | |
struct closure * closure = (struct closure *)malloc(sizeof(struct closure *)); | |
closure->x = x; | |
printf ("x is %d\n",closure->x); | |
closure->call = █ | |
return closure; | |
} | |
int main() { | |
struct closure * c = foo(5); | |
c->call(c); | |
c->call(c); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
struct env { | |
int x; | |
}; | |
struct closure { | |
void (* call)(struct env *); | |
struct env * env; | |
}; | |
void block(struct env * env) { | |
env->x += 1; | |
printf ("block: x is %d\n", env->x); | |
} | |
struct closure foo(int x) | |
{ | |
struct env * env = (struct env *)malloc(sizeof(struct env)); | |
env->x = x; | |
printf ("x is %d\n",env->x); | |
struct closure closure; | |
closure.env = env; | |
closure.call = block; | |
return closure; | |
} | |
int main() { | |
struct closure c = foo(5); | |
c.call(c.env); | |
c.call(c.env); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
struct env { | |
int x; | |
}; | |
struct closure { | |
void (* call)(struct env *); | |
struct env * env; | |
}; | |
void block(struct env * env) { | |
env->x += 1; | |
printf ("block: x is %d\n", env->x); | |
} | |
struct closure * foo(int x) | |
{ | |
struct env * env = (struct env *)malloc(sizeof(struct env)); | |
env->x = x; | |
printf ("x is %d\n",env->x); | |
struct closure * closure = (struct closure *)malloc(sizeof(struct closure *)); | |
closure->env = env; | |
closure->call = block; | |
return closure; | |
} | |
int main() { | |
struct closure * c = foo(5); | |
c->call(c->env); | |
c->call(c->env); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sys/mman.h> | |
struct env { | |
int x; | |
}; | |
struct __attribute__((packed)) thunk { | |
unsigned char push; | |
struct env * env_addr; | |
unsigned char call; | |
signed long call_offset; | |
unsigned char add_esp[3]; | |
unsigned char ret; | |
}; | |
struct thunk default_thunk = {0x68, 0, 0xe8, 0, {0x83, 0xc4, 0x04}, 0xc3}; | |
typedef void (* cfunc)(); | |
struct thunk * make_thunk(struct env * env, void * code) | |
{ | |
struct thunk * thunk = (struct thunk *)mmap(0,sizeof(struct thunk), PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
*thunk = default_thunk; | |
thunk->env_addr = env; | |
thunk->call_offset = code - (void *)&thunk->add_esp[0]; // Pretty! | |
mprotect(thunk,sizeof(struct thunk), PROT_EXEC); | |
return thunk; | |
} | |
void block(struct env * env) { | |
env->x += 1; | |
printf ("block: x is %d\n", env->x); | |
} | |
cfunc foo (int x) | |
{ | |
struct env * env = (struct env *)malloc(sizeof(struct env)); | |
env->x = x; | |
printf ("x is %d\n",env->x); | |
return (cfunc)make_thunk(env,(void *)&block); | |
} | |
int main() { | |
cfunc c = foo(5); | |
c(); | |
c(); | |
} |
Thanks for this article. I'm reading it right now in order to implement closures in my own scripting language, Sparkling (here it is). Just one thing I noticed: you should not cast the return value of malloc()
. Just write this: struct env *p = malloc(sizeof(*p));
struct closure * closure = (struct closure *)malloc(sizeof(struct closure *));
should be below ???:
struct closure * closure = (struct closure *)malloc(sizeof(struct closure));
64-bit version? :)
Blog post is here https://hokstad.com/how-to-implement-closures
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/vidarh/259462#file-closures-thunks-c-L24
Shouldn't that be PROT_WRITE, not PROT_WRITE | PROT_EXEC?