如果我转换函数指针,更改参数的数量会发生什么

sle*_*ske 10 c pointers casting function-pointers type-safety

我刚刚开始围绕C中的函数指针.要了解函数指针的转换是如何工作的,我编写了以下程序.它基本上创建了一个函数指针,该函数指向一个带有一个参数的函数,将它转换为带有三个参数的函数指针,并调用该函数,提供三个参数.我很好奇会发生什么:

#include <stdio.h>

int square(int val){
  return val*val;
}

void printit(void* ptr){
  int (*fptr)(int,int,int) = (int (*)(int,int,int)) (ptr);
  printf("Call function with parameters 2,4,8.\n");
  printf("Result: %d\n", fptr(2,4,8));
}


int main(void)
{
    printit(square);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这编译并运行时没有错误或警告(在Linux/x86上为gcc -Wall).我系统的输出是:

Call function with parameters 2,4,8.
Result: 4
Run Code Online (Sandbox Code Playgroud)

显然,多余的论点只是默默地被抛弃了.

现在我想了解这里发生了什么.

  1. 至于合法性:如果我理解正确地将函数指针强制转换为另一种类型的答案,那么这只是未定义的行为.因此,运行并产生合理结果的事实只是纯粹的运气,对吗?(或编译器编写者的好看)
  2. 为什么gcc不会警告我这个,即使是Wall?这是编译器无法检测到的东西吗?为什么?

我来自Java,那里的类型检查要严格得多,所以这种行为让我有点困惑.也许我正在经历文化冲击:-).

Fra*_*nov 15

额外的参数不会被丢弃.它们被正确放置在堆栈上,就像调用需要三个参数的函数一样.但是,由于您的函数仅关注一个参数,因此它仅查看堆栈顶部并且不会触及其他参数.

基于以下两个事实,这个呼叫有效的事实是纯粹的运气:

  • 函数和转换指针的第一个参数的类型是相同的.如果您更改函数以获取指向字符串的指针并尝试打印该字符串,您将获得一个很好的崩溃,因为代码将尝试取消引用指向内存2的指针.
  • 默认情况下使用的调用约定是调用者清理堆栈.如果更改调用约定,以便被调用者清理堆栈,最终调用者会在堆栈上推送三个参数,然后被调用者清理(或者更确切地说)尝试一个参数.这可能会导致堆栈损坏.

编译器无法通过一个简单的原因警告您这样的潜在问题 - 在一般情况下,它在编译时不知道指针的值,因此它无法评估它指向的内容.想象一下,函数指针指向在运行时创建的类虚拟表中的方法?所以,你告诉编译器它是一个指向具有三个参数的函数的指针,编译器会相信你.


小智 12

如果你把汽车扔成锤子,编译器就会告诉你汽车是锤子但你不会把汽车变成锤子.编译器可能成功地使用汽车来驱动钉子,但这是依赖于实现的好运.这仍然是一件不明智的事情.