简体   繁体   English

C:将数组传递给指针函数

[英]C: Passing array to pointer function

I'm not sure if the question has asked before, but I couldn't find any similar topics. 我不确定这个问题是否曾经问过,但是我找不到任何类似的主题。

I'm struggeling with the following piece of code. 我正在努力处理以下代码。 The idea is to extend r any time later on without writing lots of if-else statements. 想法是在以后的任何时间扩展r而不编写大量的if-else语句。 The functions ( func1 , func2 ...) either take zero or one arguments. 函数( func1func2 ...)采用零或一参数。

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;
}

The code above assumes that all functions take the string argument, which is not the case. 上面的代码假定所有函数都采用字符串参数,而事实并非如此。 The prototype for the pointer function is declared without any datatype to prevent the incompatible pointer type warning. 声明的指针函数原型没有任何数据类型,以防止出现不兼容的指针类型警告。

Passing arguments to functions that do not take any parameters usually results in too few arguments . 将参数传递给不带任何参数的函数通常会导致参数太少 But in this case the compiler doesn't 'see' this ahead, which also let me to believe that no optimization is done to exclude these unused addresses from being pushed onto the stack. 但是在这种情况下,编译器不会提前“看到”这一点,这也让我相信没有做任何优化来排除这些未使用的地址不会被压入堆栈。 (I haven't looked at the actual assemble code). (我没有看过实际的汇编代码)。

It feels wrong someway and that's usually a recipe for buffer overflows or undefined behaviour. 某种程度上感觉不对,这通常是缓冲区溢出或未定义行为的秘诀。 Would it be better to call functions without parameters separately? 分别调用不带参数的函数会更好吗? If so, how much damage could this do? 如果是这样,这会造成多大的损失?

The way to do it is typedef a function with 1 argument, so the compiler could verify if you pass the correct number of arguments and that you do not pass something absolutely incompatible (eg a struct by value). 这样做的方法是typedef一个带有1个参数的函数,因此编译器可以验证是否传递了正确数量的参数,并且没有传递绝对不兼容的内容(例如,按值构造)。 And when you initialize your array, use this typedef to cast function types. 并且在初始化数组时,请使用此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);

Here param_t is defined as uintpr_t . 在这里, param_t被定义为uintpr_t This is an integer type big enough to store a pointer value. 这是一个足够存储指针值的整数类型。 For details see here: What is uintptr_t data type . 有关详细信息,请参见此处: 什么是uintptr_t数据类型 The caveat is that the calling conventions for param_t should be compatible with the function arguments you use. 需要注意的是, param_t的调用约定应与您使用的函数参数兼容。 This is normally true for all integer and pointer types. 通常对于所有整数和指针类型都是如此。 The following sample is going to work, all the type conversions are compatible with each other in terms of calling conventions: 下面的示例将起作用,所有类型转换在调用约定方面都相互兼容:

// No problem here.
void ptr_func(struct my_struct *ptr) {
    ...
}
...
struct my_struct struct_x;
((func_t) &ptr_func)((param_t) &struct_x);

But if you are going to pass a float or double argument, then it might not work as expected. 但是,如果您要传递一个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);

In this case you might need to define a function like this: 在这种情况下,您可能需要定义如下函数:

// 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);

The float is first being converted to an integer type and then back. 浮点数首先被转换为整数类型,然后返回。 As a result the value might be rounded. 结果,该值可能会四舍五入。 An obvious solution would be to take an address of x and pass it to modified a function float_func2(float *value_ptr) . 一个明显的解决方案是获取x的地址并将其传递给函数float_func2(float *value_ptr)进行修改。 The function would dereference its pointer argument and get the actual float value. 该函数将取消引用其指针参数并获取实际的float值。

But, of course, being hardcore C-programmers we do not want to be obvious, so we are going to resort to some ugly trickery. 但是,当然,作为硬核C程序员,我们不想变得显而易见,因此我们将采取一些丑陋的诡计。

// Problem fixed the true C-programmer way.
void float_func(param_t param) {
    float value = *((float *) &param);
    ...
}
...
float x = 1.234;
((func_t) &float_func)(*((param_t *) &x));

The difference of this sample compared to passing a pointer to float, is that on the architecture (like x86-64) where parameters are passed on registers rather than on the stack, a smart enough compiler can make float_func do its job using registers only, without the need to load the parameter from the memory. 与将指针传递给float相比,此示例的不同之处在于,在将参数传递到寄存器而非堆栈上的体系结构(如x86-64)上,足够聪明的编译器可以使float_func仅使用寄存器来完成其工作,无需从内存中加载参数。

One option is for all the functions accept a char * argument, and your calling code to always pass one. 一种选择是,所有函数都接受char *参数,并且您的调用代码始终传递一个。 The functions that don't need an argument need not use the argument they receive. 不需要参数的函数不必使用它们收到的参数。

To be clean (and avoid undefined behaviour), if you must have some functions that accept no argument and some functions that accept an argument, use two lists and register/call each type of function separately. 为了保持整洁(并避免未定义的行为),如果必须具有不接受参数的某些函数和必须接受参数的某些函数,请使用两个列表并分别注册/调用每种类型的函数。

If the behaviour is undefined there's no telling how much damage could be caused. 如果行为未定义,则无法确定会造成多大的损害。

It might blow up the planet. 可能炸毁地球。 Or it might not. 否则可能不会。

So just don't do it, OK? 所以只是不要这样做,好吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM