我不确定之前是否提出过这个问题,但我找不到任何类似的主题。
我正在使用以下代码。这个想法是在以后的任何时候扩展r
而不写很多if-else语句。函数(func1
,func2
...)可以采用零个或一个参数。
void func1() {
puts("func1");
}
void func2(char *arg){
puts("func2");
printf("with arg %s\n", arg);
}
struct fcall {
char name[16];
void (*pfunc)();
};
int main() {
const struct fcall r[] = {
{"F1", func1},
{"F2", func2}
};
char param[] = "someval";
size_t nfunc = RSIZE(r); /* array size */
for(;nfunc-->0;) {
r[nfunc].pfunc(param);
}
return 0;
}
上面的代码假设所有函数都采用字符串参数,即不的情况。声明指针函数的原型没有任何数据类型,以防止不兼容的指针类型警告。
将参数传递给不带任何参数的函数通常会导致参数太少。但在这种情况下,编译器不会看到'这一点,这也让我相信没有进行任何优化来排除这些未使用的地址被推入堆栈。 (我还没看过实际的汇编代码)。
某种程度上感觉不对,这通常是缓冲区溢出或未定义行为的一种方法。最好不要分别调用没有参数的函数吗?如果是这样,这会造成多大的伤害?
答案 0 :(得分:1)
执行此操作的方法是使用1参数typedef函数,因此编译器可以验证您是否传递了正确数量的参数,并且您没有传递绝对不兼容的内容(例如,按值的结构)。初始化数组时,使用此typedef来转换函数类型。
void func1(void) { ... }
void func2(char *arg) { ... }
void func3(int arg) { ... }
typedef uintptr_t param_t;
typedef void (*func_t)(param_t);
struct fcall {
char name[16];
func_t pfunc;
};
const struct fcall r[] = {
{"F1", (func_t) func1},
{"F2", (func_t) func2}
{"F3", (func_t) func3}
};
...
r[0].pfunc((param_t) "foo");
r[1].pfunc((param_t) "bar");
r[2].pfunc((param_t) 1000);
此处param_t
定义为uintpr_t
。这是一个足以存储指针值的整数类型。有关详细信息,请参阅此处:What is uintptr_t data type。
需要注意的是param_t
的调用约定应该与您使用的函数参数兼容。对于所有整数和指针类型,这通常都是正确的。以下示例将起作用,所有类型转换在调用约定方面彼此兼容:
// No problem here.
void ptr_func(struct my_struct *ptr) {
...
}
...
struct my_struct struct_x;
((func_t) &ptr_func)((param_t) &struct_x);
但是如果要传递float或double参数,那么它可能无法正常工作。
// There might be a problem here. Depending on the calling
// conventions the value might contain a complete garbage,
// as it might be taken from a floating point register that
// was not set on the call site.
void float_func(float value) {
...
}
...
float x = 1.0;
((func_t) &float_func)((param_t) x);
在这种情况下,您可能需要定义如下函数:
// Problem fixed, but only partially. Instead of garbage
// there might be rounding error after the conversions.
void float_func(param_t param) {
float value = (float) param;
...
}
...
float x = 1.234;
((func_t) &float_func)((param_t) x);
首先将float转换为整数类型然后返回。因此,该值可能会四舍五入。一个明显的解决方案是采用x
的地址并将其传递给修改函数float_func2(float *value_ptr)
。该函数将取消引用其指针参数并获取实际的浮点值。
但是,当然,作为硬核C程序员,我们不想显而易见,所以我们将采用一些丑陋的技巧。
// Problem fixed the true C-programmer way.
void float_func(param_t param) {
float value = *((float *) ¶m);
...
}
...
float x = 1.234;
((func_t) &float_func)(*((param_t *) &x));
与将指针传递给float相比,此示例的区别在于体系结构(如x86-64),其中参数在寄存器而不是堆栈上传递,一个足够聪明的编译器可以使float_func完成其工作只注册,无需从内存中加载参数。
答案 1 :(得分:0)
一个选项是所有函数接受char *
参数,并且您的调用代码始终传递一个。不需要参数的函数不需要使用它们接收的参数。
要清理(并避免未定义的行为),如果必须有一些不接受参数的函数和一些接受参数的函数,请使用两个列表并分别注册/调用每种类型的函数。
答案 2 :(得分:0)
如果行为未定义,则无法确定可能造成多大的伤害。
可能会炸毁这个星球。或者它可能不会。
所以,不要这样做,好吗?