I'm trying to reconcile my object-oriented/functional mind with programming in a language with C. Let's say I want to achieve dynamic dispatch in C, say I want to have a collection of tasks to execute. I could make a pointer to a collection of functions I want to execute, simple enough. However, let's say that the task to be executed depends on variables that exist when the function is created, and will not be passed into the function by whatever executes them. In Java or C++, I would use an object (probably a lambda) that captures those variables, so that the function can access them when it's being executed. But I hear that function pointers cannot capture variables in standard C. So how would I implement this design pattern? Thanks.
2 Answers
On the CS StackExchange, I discussed how you can encode higher-order functions in terms of function pointers. Basically, a closure is just a pair of a void *
to an environment and a function pointer. Something like:
struct IntToIntClosure {
void *environment;
int (*func)(void *, int);
}
For example, in the Linux Asynchronous I/O API the sigevent
structure does pair these things together (the "environment" being the sigev_value
field).
However, much like it's common to pass a foo *
and a length separately when the foo *
is viewed as an array, it's common to not actually package these together in a data structure, but to pass them in as separate parameters. Many C APIs which accept callbacks take an additional "context" parameter that is usually a void *
which will be passed to the function pointer whenever it is called. For example, glibc's on_exit
(contrast to atexit
).
You can readily encode higher-order functions and objects with dynamic dispatch using these techniques. However, if you have an API that only accepts function pointers for registration, then you cannot do this*. There's no way to know which environment should be associated to which invocation of a function pointer after the fact. In this case, you'll be forced to use global or static variables to pass any environment information with the attendant limitations to essentially only one environment being allowed. If you are in control of this API, I strongly recommend you take and store an extra parameter for this environment (either explicitly or via a struct
).
* There are tricks to get around this if you don't care about portability. If you can dynamically generate function pointers (i.e. by writing machine code at run-time) you can create a function pointer to a stub that will pass the extra parameter to the desired function. This is how GHC Haskell handles passing Haskell functions as C callbacks.
-
Worth noting that if you do need to use generated functions, these are usually called "thunks" and the process of using them "thunking", and there are libraries available for generating them on most platforms– JulesCommented Apr 23, 2017 at 8:09
There's no capture possibility in C, and the function is not "created" but exists since the executable image is loaded in memory.
There are basically three ways to do what you want:
- use global variables for passing values and pointers to the function pointed to. Inconvenience: bad encapsulation.
- use static variables in a separate compilation unit, with an initialisation function and the target function to be called. This is better from the point of view of encapsulation. However you are still limited to a single set of "captured" variables.
- use a context memento: it is a variant of the previous approach, but instead of static variables, you use members of a dynamically allocated structure. The pointer to this context would be returned by the initialisation function, and would be stored together with the function pointer. This is a reentrent solution, ensuring proper encapsulation. However, this requires that the pointer is passed to the function that is called. Of course, different functions could have different context structures (subject to casting)