In article <pa****************************@consulting.net.n z>
Adam Warner <us****@consulting.net.nz> wrote:
I'm very new to C but I have a number of years of Common Lisp programming
experience. I'm trying to figure out ways of translating higher order
concepts such as closures into C. The code will not be idiomatic C.
Various people have answered the original questions; but no one has
addressed the basic idea, "how to handle closures in C" (in strictly
conforming, 100% portable C, that is).
What you need is to collect up the closed-over variables into a
data structure, and pass an explicit pointer to that data structure.
This is, of course, what Common Lisp and other closure-containing
(and continuation-passing and so on) language implementations do
internally (perhaps with optimization, and perhaps using techniques
like cactus stacks instead of heap allocation).
Here is how I usually do it. Suppose we have a family of functions
f1 through fN that all use some group(s) of variables. Define a
structure:
/*
* Variables for functions f1 through fN.
*/
struct f_context {
int xyzzy;
char plugh[100];
/* etc */
};
Then define the functions as taking either "struct f_context *" or,
if they are to be called through "generic function pointer" interfaces,
"void *" as their first (and perhaps only) parameters:
void f1(void *context) {
struct f_context *fc = context;
fc->xyzzy++;
fc->plugh[3] = 'a';
}
void f2(void *context) {
struct f_context *fc = context;
fc->xyzzy *= 3;
fc->plugh[32] = '\0';
}
/* etc */
Now each function can be called through a function-pointer of the
correct type:
void (*fp)(void *);
void *context;
...
context = <some expression that obtains the correct f_context>;
fp = f2; /* or whichever fX function */
...
fp(contextp);
State machines are typical users of such functions, with an "init"
function creating the state and, e.g., the "next function to call"
being the return value of each. A destructor function would then
return NULL:
/*
* I resort to a typedef here partly to avoid declaration ugliness.
* At the same time, though, this particular type is shared by
* whoever calls the state machine, so this typedef should probably
* appear in a shared header.
*/
typedef void (*state_func_ptr)(void *);
/* the rest of this is NOT shared, and is private to one file */
struct state_context { ... };
/* forward declarations: */
static state_func_ptr st1(void *);
static state_func_ptr st2(void *);
...
static state_func_ptr st_end(void *);
/*
* This code aborts if malloc fails -- other strategies are of
* course possible. Note that the context to be passed to the
* state functions is filled in via the supplied pointer.
*/
state_func_ptr state_init(void **vp) {
struct state_context *sc = malloc(sizeof *sc);
if (sc == NULL)
panic("out of memory in state_init");
*vp = sc;
/* st1 always gets called first, as it is the initial state */
return st1;
}
static state_func_ptr st1(void *context) {
struct state_context *sc = context;
...
return expr ? st2 : st3;
}
static state_func_ptr st2(void *context) {
struct state_context *sc = context;
...
return st_end;
}
...
static state_func_ptr st_end(void *context) {
free(context); /* release the space */
return NULL; /* and terminate the state machine */
}
Note that only the name "state_init" is exported from the entire
module -- the entire state machine is invisible to the caller,
who only knows to iterate until finished:
typedef void (*state_func_ptr)(void *); /* from shared header */
void call_it(void) {
state_func_ptr fp;
void *ctx;
/* initialize state machine */
fp = state_init(&ctx);
while (fp != NULL) {
/* any appropriate actions here */
/* call state machine and get new state */
fp = (*fp)(ctx);
/* more actions here as needed */
}
}
Other methods are of course possible, and one can expose as much
of the "context" data structure as one likes -- although in C, it
is an all-or-nothing deal: entire the contents are visible, or it
is an opaque type known either by name ("struct foo", as an incomplete
type) or dealt with via generic-data-pointer ("void *", with
conversions to and from the internal "struct whatever", as in these
examples). If you want to expose only part of the struct, expose
the whole thing, and tell people not to use the parts they are
supposed to keep their fingers off of. :-) (Or, use a language
with such features, perhaps even C++. C++ has a lot of syntactic
sugar to make this prettier, using "class" instead of "struct".
Note, however, that C++ is a much larger language, and even the
things that share syntax with C sometimes have different semantics.)
As an aside, note that it is "struct" that defines user-defined
abstract data types in C. I sometimes claim the keyword stands
for "STRange spelling for User-defined abstraCt Type", because of
this type-creating property. The poorly-named "typedef" keyword
does *not* define new types; rather, it defines aliases for existing
types. The sequence "typedef struct foo { ... } alias", which one
commonly finds in C, uses "struct foo {" to define the type --
which is thus named "struct foo" -- and then gives it an alias that
omits the "struct" keyword. If the tag (foo, in this case) is
omitted, only the alias remains; the "true name" of the type can
no longer be uttered. (I prefer to write out the struct keyword:
I find it very helpful in keeping track of which names are type
names. "struct foo" immediately tells me that foo is the type-name.
C's declaration syntax is peculiar enough that those who use typedefs
heavily wind up having to invent a convention, such as the common
"_t" suffix or FunkyCapitalLetters or ALL_CAPS, to mark which ones
are typedefs. Just use the "struct" keyword and avoid this pain,
I say; resort to typedefs only when really necessary, as for
state_func_ptr -- which could use a better name anyway.)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.