代码之家  ›  专栏  ›  技术社区  ›  Andreas Grech

将指针传递给与形式参数的要求不匹配的函数

  •  5
  • Andreas Grech  · 技术社区  · 14 年前
    int valid (int x, int y) {
        return x + y;
    }
    
    int invalid (int x) {
        return x;
    }
    
    int func (int *f (int, int), int x, int y) { 
        //f is a pointer to a function taking 2 ints and returning an int
        return f(x, y);
    }
    
    int main () {
        int val = func(valid, 1, 2),
            inval = func(invalid, 1, 2); // <- 'invalid' does not match the contract 
    
        printf("Valid:   %d\n", val);
        printf("Invalid: %d\n", inval);
    
        /*  Output:
         *  Valid:   3
         *  Invalid: 1
         */
    }
    

    在那条线上 inval = func(invalid, 1, 2); func 需要一个指向2个整数的函数的指针,而我传递一个指向一个整数的函数的指针,为什么编译器不抱怨呢?

    另外,既然发生了这种情况,第二个参数会发生什么 y invalid

    6 回复  |  直到 14 年前
        1
  •  2
  •   David Gelhar    14 年前

    为什么编译器不抱怨呢?

    warning: passing argument 1 of ‘func’ from incompatible pointer type 在这个代码上。

    另外,既然发生了这种情况,那么无效函数中的第二个参数y会发生什么变化?

    所发生的情况是,编译器会做它通常会做的任何事情来传递一个参数(把它推到堆栈上,把它放在指定的寄存器中,等等)。但是,用错误的参数数调用函数是未定义的行为,因此不能保证程序会崩溃,或者编译器会使 monkeys fly out of your nose .

        2
  •  2
  •   Scott Wales    14 年前

    假设您忽略了它应该给您的所有编译器警告,您可以这样想:

    您的代码试图调用一个函数,该函数接受两个int,并返回一个int。根据调用约定,参数可能在cpu或堆栈的寄存器中传递,输出可能会传递到寄存器。这个 valid 通话正常,一切正常。对于 invalid 调用设置相同的堆栈,使用两个参数作为程序认为调用的对象,然后调用函数。

    无效 与的第一个参数位于同一位置 有效的 ,所以 巧合的是,它做了你所期望的正确的称呼。如何清理参数是未指定的-如果被调用函数应该清理其参数的空间,则堆栈将被炸,如果调用函数清理,则程序可能继续运行。

    不管怎样,你在这里调用未定义的行为。尝试改变 func

    int func(int(*f)(int),x){return f(x);}
    

        3
  •  1
  •   anon anon    14 年前

    int func (int (*f) (int, int), int x, int y) { 
    

    您的代码中包含返回int*的函数的类型—您需要指向返回int的函数的指针。更改后,这一行:

     inval = func(invalid, 1, 2);
    

    给了我:

    fp.c:16: warning: passing argument 1 of 'func' from incompatible pointer type
    fp.c:9: note: expected 'int (*)(int,  int)' but argument is of type 'int (*)(int)'
    

    与gcc合作。你原来的代码也给了我多个警告,顺便说一句-你用的是哪种编译器?如果你的问题真的是“为什么这个代码看起来有效?”嗯,这是不确定行为的乐趣之一。

        4
  •  1
  •   Nick Dandoulakis    14 年前

    下面是一个未定义行为的示例 作品
    在你的例子中可能也发生了类似的事情。

    typedef int (*p)(int, int);
    typedef int (*p2)(int);
    
    int invalid (int x) {
        return x;
    }
    
    int func () {
       p2 f = invalid;
       return ((p)f)(1, 2);
    }
    
    // IA32 asm, "func"
    ...
    216:     p2 f = invalid;
    00402148   mov         dword ptr [ebp-4],offset @ILT+1380(invalid) (00401569)
    0040214F   mov         eax,dword ptr [ebp-4]
    00402152   mov         dword ptr [ebp-4],eax
    217:     return ((p)f)(1, 2);
    00402155   mov         esi,esp
    00402157   push        2 ; <--
    00402159   push        1 ; <--
    0040215B   call        dword ptr [ebp-4] ; "invalid" will use only "1"
    0040215E   add         esp,8 ; <-- `pop` the arguments
    ...
    
        5
  •  0
  •   Alex Budovski    14 年前

    什么 无效函数?

    编译器仍然会生成将两个参数推入其中的代码 func 为了这条线

    return f(x, y);
    

    invalid 只取一个,你无法直接看到C中的第二个参数(没有诡计)。

        6
  •  0
  •   Daniel Stutzbach Edward Leno    14 年前

    要理解它的工作原理,您需要了解一点汇编语言以及C如何使用 the stack 传递参数。您可以将堆栈可视化为一个大的板堆栈,其中每个板包含一个简单变量。在许多平台上,所有参数都在堆栈上传递。 func 将推动 y x ,呼叫 f valid 荷载 是的 通过查看堆栈顶部的两个条目。 invalid 通过查看堆栈上的顶部条目。

    下面是堆栈在invalid中的外观:

    main:     3
              uninitialized
    f:        2
              1
              invalid
    invalid:  2
              1
    

    invalid()

    这也是如何 printf 工作。它们可以接受数量可变的参数。第一个参数位于堆栈的顶部,它们可以继续向下查看堆栈,以查找所需的参数。有些系统在寄存器中传递一些参数,而不是使用堆栈,但其工作原理类似。

    在C语言的早期,函数声明根本不包含参数。事实上,如果你声明一个函数时括号之间没有任何东西,你仍然可以这样定义一个函数。例如,这可以很好地编译:

    void foo();
    void bar(void) {
            foo(5); /* foo's parameters are implicit */
    }
    

    这就是为什么把 void 参数。括号之间没有任何内容,这是一个带有隐式参数的函数。