【问题标题】:Passing a pointer to a function that doesn't match the requirements of the formal parameter将指针传递给与形参要求不匹配的函数
【发布时间】:2010-05-04 10:32:19
【问题描述】:
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 个 int 的函数的指针,而我将一个指向采用单个 int 的函数的指针传递给一个指针,为什么编译器不抱怨?

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

【问题讨论】:

  • 只是出于好奇...上面的程序输出正确吗?
  • 是的,确实如此……这就是我问的原因。
  • 您是否在编译时出现警告?

标签: c pointers function function-pointers


【解决方案1】:

为什么编译器不抱怨?

也许您需要更好的编译器? gcc 在这段代码上写着warning: passing argument 1 of ‘func’ from incompatible pointer type

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

可能发生的情况是编译器会执行通常会执行的任何操作来传递参数(将其压入堆栈,将其放入指定的寄存器等)。但是,使用错误数量的参数调用函数是未定义的行为,因此无法保证 - 程序可能会崩溃,或者编译器可能会生成 monkeys fly out of your nose

【讨论】:

    【解决方案2】:

    假设您忽略了这应该给您的所有编译器警告,您可以考虑这样会发生什么:

    您的代码正在尝试调用一个接受两个整数并返回一个的函数。根据调用约定,参数可能会在 cpu 或堆栈上的寄存器中传递,输出可能会发送到寄存器。 valid 调用工作正常,一切都在预期的地方。对于invalid 调用,设置了相同的堆栈,并使用两个参数作为程序认为它正在调用的参数,然后调用该函数。

    显然,在您的平台上,invalid 的唯一参数与valid 的第一个参数位于同一位置,因此invalid 巧合地执行了您期望正确调用它的操作。未指定如何清理参数 - 如果被调用函数应该清理其参数的空间,您的堆栈将被炸毁,如果调用函数清理,那么您的程序可能会继续运行。

    不管你在这里调用未定义的行为。尝试将func改为单参数形式

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

    看看这两个调用是否仍然有效。

    【讨论】:

      【解决方案3】:

      你想要:

      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。您的原始代码也给了我多个警告,顺便说一句-您使用的是哪个编译器?如果您的问题真的是“为什么这段代码似乎有效?”,那这就是未定义行为的乐趣之一。

      【讨论】:

      • 尼尔,你能详细说明一下吗?
      • 你说的是真的,在我的情况下,f 是一个返回指针而不是指向函数的指针的函数。但是,即使我将其更改为(*f),在运行代码方面仍然没有问题。
      【解决方案4】:

      这是一个未定义行为起作用的示例
      您的示例中可能正在发生类似的事情。

      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】:

        什么 发生在第二个参数 y 中 无效函数?

        编译器仍会生成将两个参数推入 func 内的代码

        return f(x, y);
        

        因为它不知道更好。你想用两个参数调用一个函数,它推两个。 (如果原型允许它,它确实如此)。如果您检查堆栈,您会看到它们,但由于 invalid 只需要一个,因此您无法直接看到 C 中的第二个参数(没有技巧)。

        【讨论】:

          【解决方案6】:

          在 C 中,将指针从一种类型转换为另一种类型并不是错误。但是,一个好的编译器在将错误的指针类型传递给没有显式转换的函数时会生成警告。如果您没有收到警告,我强烈建议您检查编译器设置以确保警告已打开。或者考虑使用不同的编译器。 ;-)

          要了解它的工作原理,您需要了解一点汇编语言以及 C 如何使用the stack 传递参数。您可以将堆栈可视化为一大堆盘子,其中每个盘子都包含一个简单的变量。在许多平台上,所有参数都在堆栈上传递。 func 将推送yx,调用f,然后将变量弹出。 valid 通过查看堆栈顶部的两个条目来加载 xyinvalid 通过查看堆栈的顶部条目找到 x

          下面是无效堆栈内部的样子:

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

          invalid() 接受一个参数,因此它只查看堆栈顶部(1)并将其作为参数加载。

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

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

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

          这就是为什么在声明没有参数的函数时包含void 很重要的原因。它告诉编译器该函数确实需要 no 参数。括号之间没有任何内容,它是一个带有隐式参数的函数。

          【讨论】:

          • "在声明带有参数的函数时包含 void。" - 我认为你的意思是“没有参数”。
          猜你喜欢
          • 2016-11-07
          • 2019-09-16
          • 2018-08-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-06
          • 2012-11-26
          相关资源
          最近更新 更多