(note: linking another answer that shows examples of function pointers is of no help. My question is exactly concerning about the multiple ways shown in different answers, and trying to understand the differences between them)
Context:
I'm trying to understand the correct way of passing a function as parameter to another function in C (no C++). I've seen a couple of different ways and the differences are not clear for me.
My environment
I'm running a macOS
My compiler is 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
I'm using CFLAGS=-Wall -g -O0
in my Makefile.
My code examples
The following 4 snippets all produce the same result (at least the same visible output). Note that the only difference between the samples are both in the declaration and in the call of the execute
function. I've included in quotes the way I would initially call each one of these samples just to differentiate between them (so the naming is probably be wrong).
They are really just all the 4 permutations of:
- declaring the function
execute
to receivevoid f()
orvoid (*f)()
- calling the function
execute
withexecute(print)
orexecute(&print)
Note that in all the cases, the function is invoked with f()
, not with (*f)()
. But I've tested also with (*f)()
, which yielded the same results. So 8 permutations, actually (only showing 4 here for brevity)
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; }
For all examples:
- the program compiles and runs without any error or warning
Hello
gets printed correctly- the value printed in the
%p
is the same both in the first and second print sizeof
is always 1 in the first printsizeof
is always 8 in the second print
What I've read
I've read several examples (Wikipedia, StackOverflow, other resources linked in StackOverflow answers), and lots of them show different examples. My line of questioning is exactly in order to understand those differences.
The Wikipedia article about function pointers shows an example similar to snippet 4 (simplified by me):
#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;
}
Note:
- the argument is passed as
compute_sum(sin, 0.0, 1.0)
(without&
onsin
) - the parameter is declared
double (*funcp)(double)
(with*
) - the parameter is invoked as
funcp(x)
(without*
, so no(*funcp)(x)
)
A later example in the same Wikipedia article tells us that the &
when passing the function is not required, without any further explanation:
// 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
Note:
- the argument is passed as
Call(&F, 'A')
(with&
onF
) - the parameter is declared
Fn *fn
(with*
) - the parameter is invoked as
fn(c)
(without(*fn)
)
- the argument is passed as
func(print)
(without&
onprint
) - the parameter is declared
void (*f)(int)
(with*
) - the parameter is invoked as
(*f)(ctr)
(with(*fn)
)
This answer shows 2 examples:
- on the first one is like my snippet 1:
- the argument is passed as
execute(print)
(without&
) - the parameter is declared
void f()
(without*
) - the parameter is invoked as
f()
(without*
)
- the argument is passed as
- on the second one is like my snippet 2:
- the argument is passed as
execute(&print)
(with&
) - the parameter is declared
void (*f)()
(with*
) - the parameter is invoked as
f()
(without*
)
- the argument is passed as
- there is no example on how to pass the function argument (with or without the
&
) - the parameter is declared
int (*functionPtr)(int, int)
(with*
) - the parameter is invoked as
(*functionPtr)(2, 3)
(with*
)
This linked material I found in one of the answers (is actually C++, but it doesn't use anything C++ specific concerning function pointers):
- the argument is passed as
&Minus
(with&
) - the parameter is declared
float (*pt2Func)(float, float)
(with*
) - the parameter is invoked as
pt2Func(a, b)
(without*
)
I've provided here 7 examples which provide at least 5 combinations of using or not &
and *
when passing a function as argument/receiving a function as a parameter/invoking a function received as a parameter.
What I understand from all of this
I believe that the previous section showed that there is no agreed upon explanation on the most relevant questions/answers on StackOverflow, and on other materials that I've linked (most of them linked in answers from StackOverflow).
It seems that either that all these 4 ways are handled identically by the compiler or that there are very subtle differences that don't appear in such a simple example.
I understand why the second print prints sizeof(f)
to be 8
in snippets 2 and 4. That is the pointer size in my 64-bit system). But I don't understand why the second print prints the size of a pointer even in cases where the execute
function declares the parameter without a *
(snippets 1 and 3), and don't understand why in the first print the function variable has sizeof
equal to 1
.
My questions
- Is there an actual difference between all of my 4 snippets? Why all of them behave exactly the same?
- If there is an actual runtime difference, could you explain what is happening in each one of them?
- If there is no runtime difference, could you explain what the compiler did in each case to make all of them behave the same (I assume that the compiler would see
f(myFunc)
and actually usef(&myFunc)
, or something like this. I would like to know which one is the "canonical" way).
- Which one of the 4 snippets should I use, and why?
- Should I invoke the passed function received via parameter with
f()
or with(*f)()
? - Why in the first print of each snippet, the size of the function variable (
sizeof(print)
) is always1
? What are we actually getting the sizeof in this case? (it obviously not the size of a pointer, which would be 8 bytes in my 64-bit machine. I would get the size of a pointer if I usedsizeof(&print)
). - Why on snippets 1 and 3, in the second print,
sizeof(f)
gives me 8 (the size of a pointer), even though the parameter is declared asvoid (f)()
(so without a*
, I could assume it is not a pointer).