如何在C中动态创建函数?
我尝试总结我的C问题如下:
我有一个矩阵,我希望能够使用一些函数来生成它的元素.
函数没有参数
因此我定义了以下内容:
typedef double(function)(unsigned int,unsigned int);
/* writes f(x,y) to each element x,y of the matrix*/
void apply(double ** matrix, function * f);
Run Code Online (Sandbox Code Playgroud)
现在我需要在代码中生成常量函数.我想过创建一个嵌套函数并返回它的指针,但是GCC手册(允许嵌套函数)说:
"如果你试图在包含函数退出后通过其地址调用嵌套函数,那么一切都会崩溃."
我希望从这段代码中得到...
function * createConstantFunction(const double value){
double function(unsigned int,unsigned int){
return value;
}
return &function;
}
Run Code Online (Sandbox Code Playgroud)
那么我怎样才能让它发挥作用?
谢谢!
unw*_*ind 21
C是一种编译语言.您无法在运行时"在C"中创建代码; 没有特定的C支持向内存发出指令等.您当然可以尝试分配内存,确保它是可执行的,并在那里发出原始机器代码.然后使用合适的函数指针从C调用它.
你不会从语言本身得到任何帮助,这就像生成代码并在旧的8位机器上用BASIC调用它一样.
你必须熟悉一些支持闭包机制的编程语言,不是吗?不幸的是,C不支持像它本身那样的闭包.
如果你坚持关闭,你可以找到一些模拟C中闭包的有用库.但是大多数这些库都是复杂的,并且依赖于机器.
或者,您可以改变主意,同意C-style closure
如果您可以更改签名double ()(unsigned,unsigned);
.
在C中,函数本身没有数据(或上下文),除了它的参数和它可以访问的静态变量.
所以上下文必须由你自己传递.以下是使用额外参数的示例:
// first, add one extra parameter in the signature of function.
typedef double(function)(double extra, unsigned int,unsigned int);
// second, add one extra parameter in the signature of apply
void apply(double* matrix,unsigned width,unsigned height, function* f, double extra)
{
for (unsigned y=0; y< height; ++y)
for (unsigned x=0; x< width ++x)
matrix[ y*width + x ] = f(x, y, extra);
// apply will passing extra to f
}
// third, in constant_function, we could get the context: double extra, and return it
double constant_function(double value, unsigned x,unsigned y) { return value; }
void test(void)
{
double* matrix = get_a_matrix();
// fourth, passing the extra parameter to apply
apply(matrix, w, h, &constant_function, 1212.0);
// the matrix will be filled with 1212.0
}
Run Code Online (Sandbox Code Playgroud)
是double extra
够了吗?是的,但仅限于这种情况.
如果需要更多背景,我们该怎么做?
在C中,通用参数是void*
,我们可以通过传递上下文的地址来传递任何上下文通过一个void*参数.
这是另一个例子:
typedef double (function)(void* context, int, int );
void apply(double* matrix, int width,int height,function* f,void* context)
{
for (int y=0; y< height; ++y)
for (int x=0; x< width ++x)
matrix[ y*width + x ] = f(x, y, context); // passing the context
}
double constant_function(void* context,int x,int y)
{
// this function use an extra double parameter \
// and context points to its address
double* d = context;
return *d;
}
void test(void)
{
double* matrix = get_a_matrix();
double context = 326.0;
// fill matrix with 326.0
apply( matrix, w, h, &constant_function, &context);
}
Run Code Online (Sandbox Code Playgroud)
(function,context) pair
就像&constant_function,&context
是C-style closure
.
需要闭包的每个函数(F)必须有一个上下文参数,该参数将作为其上下文传递给闭包.并且F的调用者必须使用正确的(f,c)对.
如果您可以更改函数的签名以适应C风格的闭包,那么您的代码将是简单且与机器无关的.
如果不能(功能和申请不是你写的),试着说服他改变他的代码.
如果失败,您别无选择,只能使用一些闭包库.
由于您想生成一个遵循简单配方的函数,因此对于一些内联汇编和一块可执行/可写内存来说,这应该不会太棘手。
这种方法感觉有点hacky,所以我不会在生产代码中推荐它。由于使用内联汇编,此解决方案仅适用于 Intel x86-64 / AMD64,并且需要转换为适用于其他架构。
与其他基于 JIT 的解决方案相比,您可能更喜欢它,因为它不依赖于任何外部库。
如果您想更详细地解释以下代码的工作原理,请发表评论,我会添加它。
出于安全原因,应在PROT_READ|PROT_EXEC
生成函数后标记代码页(请参阅mprotect)。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/mman.h>
int snippet_processor(char *buffer, double value, int action);
enum snippet_actions {
S_CALC_SIZE,
S_COPY,
};
typedef double (*callback_t) (unsigned int, unsigned int);
int main(int argc, char **argv) {
unsigned int pagesize = 4096;
char *codepage = 0;
int snipsz = 0;
callback_t f;
/* allocate some readable, writable and executable memory */
codepage = mmap(codepage,
pagesize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE,
0,
0);
// generate one function at `codepage` and call it
snipsz += snippet_processor(codepage, 12.55, S_COPY);
f = (callback_t) (codepage);
printf("result :: %f\n", f(1, 2));
/* ensure the next code address is byte aligned
* - add 7 bits to ensure an overflow to the next byte.
* If it doesn't overflow then it was already byte aligned.
* - Next, throw away any of the "extra" bit from the overflow,
* by using the negative of the alignment value
* (see how 2's complement works.
*/
codepage += (snipsz + 7) & -8;
// generate another function at `codepage` and call it
snipsz += snippet_processor(codepage, 16.1234, S_COPY);
f = (callback_t) (codepage);
printf("result :: %f\n", f(1, 2));
}
int snippet_processor(char *buffer, double value, int action) {
static void *snip_start = NULL;
static void *snip_end = NULL;
static void *double_start = NULL;
static int double_offset_start = 0;
static int size;
char *i, *j;
int sz;
char *func_start;
func_start = buffer;
if (snip_start == NULL) {
asm volatile(
// Don't actually execute the dynamic code snippet upon entry
"jmp .snippet_end\n"
/* BEGIN snippet */
".snippet_begin:\n"
"movq .value_start(%%rip), %%rax\n"
"movd %%rax, %%xmm0\n"
"ret\n"
/* this is where we store the value returned by this function */
".value_start:\n"
".double 1.34\n"
".snippet_end:\n"
/* END snippet */
"leaq .snippet_begin(%%rip), %0\n"
"leaq .snippet_end(%%rip), %1\n"
"leaq .value_start(%%rip), %2\n"
:
"=r"(snip_start),
"=r"(snip_end),
"=r"(double_start)
);
double_offset_start = (double_start - snip_start);
size = (snip_end - snip_start);
}
if (action == S_COPY) {
/* copy the snippet value */
i = snip_start;
while (i != snip_end) *(buffer++) = *(i++);
/* copy the float value */
sz = sizeof(double);
i = func_start + double_offset_start;
j = (char *) &value;
while (sz--) *(i++) = *(j++);
}
return size;
}
Run Code Online (Sandbox Code Playgroud)
一种方法是使用您想要的函数集编写标准C文件,通过gcc编译它并将其作为动态库加载以获取指向函数的指针.
最终,如果你能够指定你的函数而不必动态定义它们可能会更好(例如通过具有通用模板函数来获取定义其特定行为的参数).
#include <stdio.h>
#include <stdarg.h>
#include <callback.h>
static double internalDoubleFunction(const double value, ...) {
return value;
}
double (*constDoubleFunction(const double value))() {
return alloc_callback(&internalDoubleFunction, value);
}
main() {
double (*fn)(unsigned int, unsigned int) = constDoubleFunction(5.0);
printf("%g\n", (*fn)(3, 4));
free_callback(fn);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
(未经测试,因为我当前没有安装FFCALL,但我记得它的工作原理是这样的.)