为什么这个函数指针在没有警告或错误的情况下工作?

Vin*_*uda 22 c function-pointers

知道这个电话:

pow(4);
Run Code Online (Sandbox Code Playgroud)

将生成此错误消息:

 error: too few arguments to function ‘pow’
Run Code Online (Sandbox Code Playgroud)

我正在学习函数的指针,当看到下面的代码工作时,我感到很惊讶.但为什么?

#include<stdio.h>
#include<math.h>

void aux(double (*function)(), double n, double x);

int main(void)
{
    aux(pow, 4, 2); 

    aux(sqrt, 4, 0);

    return 0;
}

void aux(double (*function)(double), double n, double x)
{   
    if(x == 0)
        printf("\nsqrt(%.2f, %.2f): %f\n", n, x, (*function)(n));  
    else
        printf("\npow(%.2f, %.2f): %f\n", n, x, (*function)(n));  
}
Run Code Online (Sandbox Code Playgroud)

我编译使用:

gcc -Wall -Wextra -pedantic -Wconversion -o test test.c -lm
Run Code Online (Sandbox Code Playgroud)

结果是:

pow(4.00, 2.00): 16.000000

sqrt(4.00, 0.00): 2.000000
Run Code Online (Sandbox Code Playgroud)

如果我将第一次调用的第三个参数更改aux为3,结果将更改为:

pow(4.00, 3.00): 64.000000

sqrt(4.00, 0.00): 2.000000
Run Code Online (Sandbox Code Playgroud)

还有一个问题.在这种情况下,声明和使用函数指针的正确方法是什么?

Kei*_*son 37

这个:

void aux(double (*function)(), double n, double x);
Run Code Online (Sandbox Code Playgroud)

使用旧式非原型声明function.空括号()表示该函数采用固定但未指定的数字和类型的参数.

C仍然允许这种声明以实现向后兼容.ANSI C在1989年引入了原型(指定参数类型的函数声明).在此之前,无法在函数声明中指定参数类型,并且编译器无法检查调用是否传递了正确的数字和参数的类型.

这样的声明是"过时的",这意味着可以从未来的C标准中删除对它们的支持(但是在20多年的时间里,委员会还没有去除它们).调用具有错误数量的参数类型的函数不一定会由编译器诊断,并且行为未定义.

当一个有原型而另一个没有原型时,函数类型兼容性的规则有点复杂.这些类型:

double(double)         /* function with one double parameter
                          returning double */
double(double, double) /* function with two double parameters
                          returning double */
Run Code Online (Sandbox Code Playgroud)

彼此不兼容,但它们兼容此类型:

double()   /* function with a fixed but unspecified number of parameters
              returning double */
Run Code Online (Sandbox Code Playgroud)

这使得在没有编译器诊断的情况下进行错误调用成为可能.

要避免此问题,请始终使用原型:

void aux(double (*function)(double, double), double n, double x);
Run Code Online (Sandbox Code Playgroud)

您不仅可以从编译器获得更好的诊断,而且不必担心非原型函数的复杂兼容性规则(如果您好奇,则在N1570 6.7.6.3第16段中指定).

  • @haccks:你可以永远怀疑.行为是**未定义**.原则上,下次运行程序时可能会更改.您无法预测它,您只能通过反汇编二进制文件来检查编译器实际生成的代码.您不能假设编译器在下次编译程序时会生成相同的代码. (6认同)
  • 仍然想知道如何只有一个参数导致`pow`或`sqrt`计算? (2认同)

Joh*_*ger 9

你的功能原型aux()......

void aux(double (*function)(), double n, double x);
Run Code Online (Sandbox Code Playgroud)

...指定第一个参数是指向返回double和接受未指定参数的函数的指针.这可以防止GCC向该函数的调用发出有关不匹配类型的警告main().

但是,函数aux()定义为其第一个参数提供了更具体的类型,该参数与您传递的实际参数不兼容.通过指针调用这些函数具有未定义的语义.几乎任何事情都可能发生,包括行为似乎是你想要的.你不能依赖任何关于这种行为的东西.


mya*_*aut 8

因为您指定了aux()main之前的原型并且function没有任何指定的参数类型.了解差异:

void f();   /* Accepts any number of arguments thanks to K&R C */
void g(void); /* No arguments accepted */
void h(int); /* Only one integer argument accepted */
Run Code Online (Sandbox Code Playgroud)

如果您将aux()原型声明为:

void aux(double (*function)(double), double n, double x);
Run Code Online (Sandbox Code Playgroud)

海湾合作委员会开始抱怨.


dat*_*olf 5

一对空括号()告诉C编译器可以使用任意数量的参数调用该函数,但该函数本身具有其原型的特定数量的参数.因此,如果将它与函数指针一起使用,并且传递的参数的数量和相应类型与指向函数的原型匹配,则一切都将起作用.如果你饿了参数列表或使用不同类型的值,那么......那是未定义的行为.