在C中传递函数还是传递函数指针?

拉斐尔·埃英(Rafael Eyng)

(注意:链接另一个显示函数指针示例的答案无济于事。我的问题恰恰是关于不同答案中显示的多种方式,并试图理解它们之间的差异)

内容:

我试图了解将函数作为参数传递给C中其他函数的正确方法(无C ++)。我已经看到了几种不同的方式,但这些差异对我来说并不明显。

我的环境

我正在运行macOS

我的编译器是GCC:

$ gcc --version
Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

CFLAGS=-Wall -g -O0在我的Makefile中使用。

我的代码示例

以下4个摘要均产生相同的结果(至少具有相同的可见输出)。请注意,样本之间的唯一区别在于execute函数的声明和调用我已经在引号中包括了我最初调用这些样本中的每个样本的方式,只是为了区分它们(因此命名可能是错误的)。

它们实际上只是以下四个排列的全部:

  • 声明execute要接收的功能void f()void (*f)()
  • executeexecute(print)调用函数execute(&print)

请注意,在所有情况下,该函数都是通过f()而不是调用的(*f)()但是我也用(*f)()进行了测试,得出了相同的结果。因此,实际上是8个排列(为简洁起见,这里仅显示4个)

  • 片段1:“传递而不指针,接收指针”

    #include <stdio.h>
    
    void execute(void f()) {
        printf("2 %p %lu\n", f, sizeof(f));
        f();
    }
    
    void print() {
        printf("Hello!\n");
    }
    
    int main() {
        printf("1 %p %lu\n", print, sizeof(print));
        execute(print);
        return 0;
    }
    
  • 片段2:“通过一个指针,并接收一个指针”

    #include <stdio.h>
    
    void execute(void (*f)()) {
        printf("2 %p %lu\n", f, sizeof(f));
        f();
    }
    
    void print() {
        printf("Hello!\n");
    }
    
    int main() {
        printf("1 %p %lu\n", print, sizeof(print));
        execute(&print);
        return 0;
    }
    
  • 片段3:“通过一个指针,并且接收没有一个指针”

    #include <stdio.h>
    
    void execute(void (f)()) {
        printf("2 %p %lu\n", f, sizeof(f));
        f();
    }
    
    void print() {
        printf("Hello!\n");
    }
    
    int main() {
        printf("1 %p %lu\n", print, sizeof(print));
        execute(&print);
        return 0;
    }
    
  • 片段4:“传递没有一个指针,并接收一个指针”

    #include <stdio.h>
    
    void execute(void (*f)()) {
        printf("2 %p %lu\n", f, sizeof(f));
        f();
    }
    
    void print() {
        printf("Hello!\n");
    }
    
    int main() {
        printf("1 %p %lu\n", print, sizeof(print));
        execute(print);
        return 0;
    }
    

对于所有示例:

  • 程序编译并运行,没有任何错误或警告
  • Hello 正确打印
  • %p第一张和第二张中打印的值相同
  • sizeof 在第一个打印中始终为1
  • sizeof 在第二个打印中始终为8

我读过的

我已经阅读了几个示例(Wikipedia,StackOverflow,以及StackOverflow答案中链接的其他资源),其中有很多显示了不同的示例。我的质询恰恰是为了理解这些差异

约函数指针维基百科文章示出了类似于代码段4的例子(通过我简化):

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

double compute_sum(double (*funcp)(double), double lo, double hi) {
  // ... more code
  double y = funcp(x);
  // ... more code
}

int main(void) {
  compute_sum(sin, 0.0, 1.0);
  compute_sum(cos, 0.0, 1.0);
  return 0;
}

注意:

  • 参数作为compute_sum(sin, 0.0, 1.0)不带 &on sin传递
  • 声明参数double (*funcp)(double)带有 *
  • 该参数的调用方式为funcp(x)不带 *,因此不带(*funcp)(x)

同一篇Wikipedia文章中的下一个示例告诉我们,&不需要传递函数的时间,没有任何进一步的解释:

// This declares 'F', a function that accepts a 'char' and returns an 'int'. Definition is elsewhere.
int F(char c);

// This defines 'Fn', a type of function that accepts a 'char' and returns an 'int'.
typedef int Fn(char c);

// This defines 'fn', a variable of type pointer-to-'Fn', and assigns the address of 'F' to it.
Fn *fn = &F;      // Note '&' not required - but it highlights what is being done.

// ... more code

// This defines 'Call', a function that accepts a pointer-to-'Fn', calls it, and returns the result
int Call(Fn *fn, char c) {
  return fn(c);
} // Call(fn, c)

// This calls function 'Call', passing in 'F' and assigning the result to 'call'
int call = Call(&F, 'A');   // Again, '&' is not required

// ... more code

注意:

  • 参数作为Call(&F, 'A') &on F传递
  • 声明参数Fn *fn带有 *
  • 参数调用为fn(c)不带 (*fn)

这个答案

  • 参数作为func(print)不带 &on print传递
  • 声明参数void (*f)(int)带有 *
  • 该参数作为(*f)(ctr) (*fn)调用

该答案显示了2个示例:

  • 第一个就像我的片段1:
    • 参数作为execute(print)不带 &传递
    • 声明参数void f()不带 *
    • 参数调用为f()不带 *
  • 第二个就像我的片段2:
    • 参数作为execute(&print) &传递
    • 声明参数void (*f)()带有 *
    • 参数调用为f()不带 *

这个答案

  • 没有关于如何传递函数参数的示例(带或不带&
  • 声明参数int (*functionPtr)(int, int)带有 *
  • 该参数作为(*functionPtr)(2, 3) *调用

我在一个答案中找到了这种链接的材料(实际上是C ++,但是它没有使用任何有关函数指针的C ++特有的东西):

  • 参数作为&Minus &传递
  • 声明参数float (*pt2Func)(float, float)带有 *
  • 参数调用为pt2Func(a, b)不带 *

我在这里提供了7个示例,这些示例提供了使用或不使用&以及*将函数作为参数传递/将函数作为参数接收/将函数作为参数接收的至少5种组合

我从这一切中了解到

我相信上一节表明,关于StackOverflow上最相关的问题/答案以及我已链接的其他资料(大多数都链接在StackOverflow的答案中),没有达成共识的解释。

似乎所有这四种方式都由编译器完全相同地处理,或者在如此简单的示例中没有出现非常细微的差异。

我知道为什么第二张打印sizeof(f)8在摘要2和摘要4中进行打印。这就是我的64位系统中的指针大小)。但是我不明白,即使在execute函数声明参数不带a的情况下*(代码段1和3),第二张打印结果也会打印出指针的大小,也不明白为什么在第一张打印结果中函数变量sizeof等于1

我的问题

  1. 我的所有4个摘要之间是否存在实际差异?为什么它们的行为完全相同?
    • 如果实际运行时存在差异,您能否解释一下其中每个发生了什么?
    • 如果没有运行时差异,您能否解释一下每种情况下编译器所做的事情,以使它们的行为都相同(我假设编译器将看到f(myFunc)并实际使用f(&myFunc),或类似的东西。我想知道哪个是“规范”方式)。
  2. 我应该使用4个摘要中的哪一个,为什么?
  3. 我应该使用f()调用通过参数接收的传递函数(*f)()吗?
  4. 为什么在每个代码段的第一次打印中,函数变量(sizeof(print))的大小始终为1在这种情况下,我们实际得到的sizeof是多少?(显然不是指针的大小,在我的64位计算机上为8字节。如果使用,我将获得指针的大小sizeof(&print))。
  5. 为什么在摘要1和3的第二张中sizeof(f),即使参数被声明为void (f)()(因此不带*,我也可以认为它不是指针)却给我8(指针的大小)。
dbush

我的所有4个摘要之间是否存在实际差异?为什么它们的行为完全相同?

所有四个代码段都是相同的。

关于如何execute调用,这在C11标准的6.3.2.1p4中涵盖

函数指示符是具有函数类型的表达式。除非是运算sizeof符,_Alignof运算符或一元运算&的操作数,否则将类型为“函数返回类型”的函数指定符转换为具有类型为“函数返回指针的指针”的表达式。

因此,因此调用execute(print)execute(&print)都相同。

关于参数execute,这将在6.7.6.3p8节中介绍:

将参数声明为“函数返回类型”声明调整为“指向函数返回类型的指针”,如6.3.2.1中所述

因此,这意味着void execute(void (*f)())void execute(void f())相同。

我应该使用4个摘要中的哪一个,为什么?

这往往是样式问题,但是我个人将变量和参数声明为指向函数的指针类型,而不是函数类型,并且我将传递函数名而不使用address-of运算符。

我应该使用f()或(* f)()调用通过参数接收的传递函数

这也是一个风格问题。我会f()比较容易阅读。

为什么在每个代码段的第一张图中,函数变量(sizeof(print))的大小始终为1?在这种情况下,我们实际得到的sizeof是多少?(显然不是指针的大小,在我的64位计算机上为8字节。如果使用sizeof(&print),我将获得指针的大小)

sizeof根据第6.5.3.4p1节,明确禁止在功能指示符上使用

sizeof操作者不得应用于具有功能类型的表达式或不完整的类型,这样的类型的括号名称,或者将指定的位字段构件的表达式。_Alignof经营者不得被施加到函数类型或不完全类型。

这样做会引起未定义的行为

为什么在代码片段1和3的第二张打印纸中,即使参数声明为void(f)(),sizeof(f)还是给我8(指针的大小)(所以如果没有*,我可以假定它不是指针)

这可以回溯到上面的6.7.6.3p8,即将function类型的函数参数转换为类型为function的指针,所以这就是您要得到的大小。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章