背景:
我正在尝试理解在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
我在我的Makefile中使用CFLAGS=-Wall -g -O0
。
我的代码示例
以下4个片段都会产生相同的结果(至少是相同的可见输出)。请注意,这些样本之间唯一的区别在于execute
函数的声明和调用。我在引号中包含了我最初调用每个样本的方式,只是为了区分它们(因此命名可能不正确)。
它们实际上只是execute
函数的以下4种排列组合:
- 将函数
execute
声明为接收void f()
或void (*f)()
- 使用
execute(print)
或execute(&print)
调用函数execute
请注意,在所有情况下,函数都是使用f()
调用的,而不是(*f)()
。实际上有8种排列组合(仅出于简洁起见,此处仅显示4种)
snippet 1: "passing without a pointer, receiving without a pointer"
#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; }
snippet 2: "passing with a pointer, and receiving with a pointer"
#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; }
snippet 3: "passing with a pointer, and receiving without a pointer"
#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; }
snippet 4: "passing without a pointer, and receiving with a pointer"
#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
我读到了什么
我读了几个例子(维基百科,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)
(不需要&
在sin
前面) - 参数声明为
double (*funcp)(double)
(需要*
) - 参数调用为
funcp(x)
(不需要*
,因此不需要(*funcp)(x)
)
&
,但没有做进一步的解释:// 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')
(F 带有&
) - 参数声明为
Fn *fn
(带有*
) - 参数调用为
fn(c)
(不带(*fn)
)
这个答案:
- 参数传递为
func(print)
(不带&
) - 参数声明为
void (*f)(int)
(带有*
) - 参数调用为
(*f)(ctr)
(带有(*fn)
)
这个答案 显示了两个示例:
- 第一个示例类似于我的代码片段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的答案中)。
似乎编译器处理所有这4种方式时要么相同,要么存在非常微妙的差异,在这样一个简单的示例中不会出现。
我理解为什么第二个打印在代码段2和4中将sizeof(f)
打印为8
。那是我的64位系统中指针大小)。但我不明白为什么第二个打印在execute
函数声明参数而没有*
的情况下也会打印指针的大小(代码段1和3),并且不理解为什么在第一个打印中函数变量的sizeof
等于1
。
我的问题
- 这4个代码片段之间是否存在实际差异?为什么它们的行为都完全相同?
- 如果存在实际运行时差异,您能解释一下每个代码片段中发生了什么吗?
- 如果没有运行时差异,您能解释编译器在每种情况下执行了什么操作以使它们的行为相同吗(我假设编译器会看到
f(myFunc)
并实际使用f(&myFunc)
或类似的内容。我想知道哪种是“规范”的方式)。
- 这4个代码片段中应该使用哪一个,为什么?
- 我应该使用
f()
还是(*f)()
来调用通过参数接收的传递函数? - 为什么在每个代码片段的第一个打印输出中,函数变量的大小(
sizeof(print)
)总是1
?在这种情况下,我们实际上得到的是什么sizeof
?(显然不是指针的大小,在我的64位机器上,指针的大小为8个字节。如果我使用sizeof(&print)
,我将得到指针的大小)。 - 为什么在代码片段1和3中,在第二个打印输出中,
sizeof(f)
给出了8(指针的大小),尽管参数声明为void (f)()
(因此没有*
,我可以假设它不是一个指针)。