I'm 99% sure it's not possible with pure C89. In order to do this, you have to create a new function pointer at runtime. There are two ways to get a function pointer: from a function expression, or from a standard library function that returns a function pointer. Function expressions refer to functions defined at compile time, so that won't work. The only standard library function that returns a function pointer is signal
, and that's no help because you only get out of it what you put in.
The only other way to get a new function pointer would be to convert a pointer to an object type into a function pointer, and that's not portable because it's not in the list of pointer conversions that you can do (however, it is noted as a common extension).
For a little while I thought you might be able to get somewhere with setjmp
and longjmp
, but that just replaces the problem of storing the double
with the problem of storing the jmp_buf
.
I did get something that happens to work on my system today, but since it's nonportable even upgrading my compiler might break it. The general idea is to create a structure that contains the pointer to the original function, the double z
, and some machine code to access that information and call the original. I don't suggest you use this, but I found it interesting. I've pointed out some of the unportable assumptions in the comments.
/* platform-specific include */
#include <sys/mman.h>
/* standard includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
double func(double x, double z)
{
printf("%g\n", z);
return x;
}
double bar(double x)
{
printf("y\n");
return x;
}
double call(double (*f)(double))
{
return f(0.0);
}
struct cDblDblRetDbl
{
double (*function)(double, double);
double a;
char code[1];
double pad;
};
static double helper(double x)
{
/* Casting a function pointer to an object pointer is
* not provided by the standard.
* In addition, this only works because the compiler
* happens to use RIP-relative addressing, so "helper"
* points to the beginning of the currently executing
* function, which is actually a copy of the one in
* the object file.
* It's worth noting explicitly that nothing in the
* C standard says that a pointer to a function will
* point to its machine code.
*/
double *dp = (double *) helper;
struct cDblDblRetDbl *data;
/* Modify it to point after the structure.
* This depends on the alignment and padding of the
* structure, which is not portable.
*/
dp += 2;
data = (struct cDblDblRetDbl *) dp;
/* back it up to the structure */
--data;
/* now call the function with the saved data. */
return data->function(x, data->a);
}
/* There is no way to get the code size of a function,
* so this is very nonportable.
* I found it by examining the object file.
*/
#define CODE_SIZE 0x60
double (*curryDoubleDoubleReturningDouble(double (*function)(double, double), double a))(double)
{
size_t size = sizeof(struct cDblDblRetDbl) + CODE_SIZE;
/* valloc is nonstandard but we need an area aligned to a
* page boundary for mprotect.
*/
struct cDblDblRetDbl *result = valloc(size);
result->function = function;
result->a = a;
/* Copy the code of the helper function into the structure.
* Once again, we're casting a function pointer to an
* object pointer and the standard doesn't say you can do that.
*/
memcpy(result->code, (void *) helper, CODE_SIZE);
/* Memory protection is out of the scope of the standard,
* and in a real program we need to check the return value.
*/
mprotect(result, CODE_SIZE, PROT_READ | PROT_EXEC | PROT_WRITE);
/* Casting an object pointer to a function pointer is also
* not provided by the standard.
* This example leaks memory.
*/
return (double(*)(double)) result->code;
}
int main()
{
call(bar);
call(curryDoubleDoubleReturningDouble(func, 5));
call(curryDoubleDoubleReturningDouble(func, 7));
call(curryDoubleDoubleReturningDouble(func, 42.9));
}
If you wrote helper
in assembly and created OS-specific versions of curryDoubleDoubleReturningDouble
, you could probably get this working a lot of places. But I'm sure there are some computers C runs on where you can't do this.