4

(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 receive void f() or void (*f)()
  • calling the function execute with execute(print) or execute(&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 print
  • sizeof 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 & on sin)
  • 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 & on F)
  • the parameter is declared Fn *fn (with *)
  • the parameter is invoked as fn(c) (without (*fn))

This answer:

  • the argument is passed as func(print) (without & on print)
  • 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 *)
  • 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 *)

This answer:

  • 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

  1. 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 use f(&myFunc), or something like this. I would like to know which one is the "canonical" way).
  2. Which one of the 4 snippets should I use, and why?
  3. Should I invoke the passed function received via parameter with f() or with (*f)()?
  4. Why in the first print of each snippet, the size of the function variable (sizeof(print)) is always 1? 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 used sizeof(&print)).
  5. 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 as void (f)() (so without a *, I could assume it is not a pointer).
dbush
  • 205,898
  • 23
  • 218
  • 273
Rafael Eyng
  • 4,720
  • 2
  • 33
  • 34
  • 3
    Functions decay into function pointers, much like arrays decay into object pointers. This should answer your question: [Why do function pointer definitions work with any number of ampersands '&' or asterisks '*'?](https://stackoverflow.com/questions/6893285/why-do-function-pointer-definitions-work-with-any-number-of-ampersands-or-as). – Lundin Jan 30 '20 at 13:39
  • 1
    Closed because of being "not focused". The definition of "not focused" in the site is "if your question has many valid answers (but no way to determine which - if any - are correct)". I doesn't seem to apply here, I'm not asking for an opinion, but for an explanation. The fact that I've broken into 5 smaller questions is just a way to guide the answer. If I had to create 5 different questions with the same long contextualization, it would be way worse than grouping it all. – Rafael Eyng Jan 30 '20 at 16:18
  • while I partly agree with you, the "Which one of the 4 snippets should I use, and why?" bit may be considered too broad/opinionated, same for question 3 – YakovL Jan 30 '20 at 18:48
  • @YakovL I understand your point now. But when I asked, I still didn't know that they were all the same. So the answer here is "it doesn't matter, they are all the same, is a matter of style", the answer is not "use the first way". So, again, it isn't opinionated. – Rafael Eyng Jan 31 '20 at 15:07

5 Answers5

4

Is there an actual difference between all of my 4 snippets? Why all of them behave exactly the same?

All four code snippets are the same.

With regard to how execute is called, this is covered in section 6.3.2.1p4 of the C11 standard:

A function designator is an expression that has function type. Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’.

So calling execute(print) or execute(&print) are the same because of this.

Regarding the parameter to execute, this is covered in section 6.7.6.3p8:

A declaration of a parameter as ‘‘function returning type’’ shall be adjusted to ‘‘pointer to function returning type’’, as in 6.3.2.1

So this means void execute(void (*f)()) and void execute(void f()) are the same.

Which one of the 4 snippets should I use, and why?

This tends to be a matter of style, but I would personally declare variables and parameters as a pointer-to-function type rather than a function type, and I would pass the function name without the address-of operator.

Should I invoke the passed function received via parameter with f() or with (*f)()

This is also a matter of style. I would go with f() as its easier to read.

Why in the first print of each snippet, the size of the function variable (sizeof(print)) is always 1? 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 used sizeof(&print))

Using sizeof on a function designator is explicitly disallowed as per section 6.5.3.4p1:

The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member. The _Alignof operator shall not be applied to a function type or an incomplete type.

And doing so invokes undefined behavior.

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 as void (f)() (so without a *, I could assume it is not a pointer)

This goes back to 6.7.6.3p8 above, namely that a function parameter of type function is converted to type pointer-to-function, so that's what you're getting the size of.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Great answer. Could you provide which version of the standard you are citing? And a link to it would make it perfect – Rafael Eyng Jan 30 '20 at 16:52
  • 1
    @RafaelEyng It’s C11. I did put a link but it wasn’t specified correctly. You should see the link now. – dbush Jan 30 '20 at 16:55
3

From the C Standard (6.3.2.1 Lvalues, arrays, and function designators)

4 A function designator is an expression that has function type. Except when it is the operand of the sizeof operator65) or the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression that has type ‘‘pointer to function returning type’’

And (6.7.6.3 Function declarators (including prototypes))

8 A declaration of a parameter as ‘‘function returning type’’ shall be adjusted to ‘‘pointer to function returning type’’, as in 6.3.2.1.

So for example these two function declarations

void execute(void f( void ) );

and

void execute(void ( *f )( void ) );

declare the same one function and may be present in one translation unit though one of them is redundant.

Pay attention to that you may not apply the sizeof operator to a function designator.

From the C Standard (6.5.3.4 The sizeof and alignof operators)

1 The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member. The alignof operator shall not be applied to a function type or an incomplete type.

But you may apply it to a function pointer.

Also as a function designator is implicitly converted to function pointer then you may apply several dereferencing operators * to a function designator used in an expression.

Here is a demonstrative program

#include <stdio.h>

void execute( void ( *f )( void ) ); 

void execute(void f( void ) ) 
{
    f();
}

void print( void ) 
{
    puts( "Hello" );
}

int main( void ) 
{
    execute( **********print );

    return 0;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
3

Formally, this rule applies to the calling code, C17 6.3.2.1/4:

A function designator is an expression that has function type. Except when it is the operand of the sizeof operator, or the unary & operator, a function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".

Informally this might be referred to as "function decay" or similar, since it works pretty much like "array decay".

In your case print is the function designator in the calling code. If you just type print, then the above rule kicks in and turns the function into a function pointer. If you type &print, that's one exception of the above rule, but you end up with a function pointer regardless.

So in practice print or &print is just a matter of style.


Inside the function, then formally C17 6.7.6.3/8 has an identical rule for function parameters:

A declaration of a parameter as "function returning type" shall be adjusted to "pointer to function returning _type", as in 6.3.2.1.

If you type void(*f)() the above does not apply, but then you end up with a function pointer anyway.


To sum it up, it's pretty much impossible to use an actual function as part of an expression, because it always decays to a function pointer. And you can't pass functions to other functions, only function pointers.

  1. Is there an actual difference between all of my 4 snippets?

The only difference is style and syntax. The differences are only interesting from a "language lawyer" perspective - from an application programmer perspective they are identical.

If there is an actual runtime difference

No.

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 use f(&myFunc), or something like this. I would like to know which one is the "canonical" way).

Disassemble them and you'll find identical machine code. The "canonical way" is none of them.

First you have the problem with empty parenthesis, which means "accept any parameter". That's obsolete style in C and should not be used. Not to be confused with C++ where (void) and () are identical. This needs to be fixed. Then, do as below:

  1. Which one of the 4 snippets should I use, and why?

The canonical version is:

typedef void func_t (void);
...
void execute(func_t* f);

Alternative canonical style, also acceptable and perhaps more common:

typedef void (*func_t) (void);
...
void execute(func_t f);

Use either of those two forms. As for the calling code, it doesn't matter if you use print or &print, it's subjective coding style.

  1. Should I invoke the passed function received via parameter with f() or with (*f)()?

f() is far more readable so you should use that.

  1. Why in the first print of each snippet, the size of the function variable (sizeof(print)) is always 1? What are we actually getting the sizeof in this case?

It isn't valid C, so it's anyone's guess how your compiler let it through. It's a blatant constraint violation of C language 6.5.3.4 "The sizeof operator shall not be applied to an expression that has function type".

So your compiler either sucks or is configured to remain in "sucky mode". I'd recommend to use gcc and configure it to be a C compiler with gcc -std=c11 -pedantic-errors.

5.

See 4).

Lundin
  • 195,001
  • 40
  • 254
  • 396
3
  1. No, no difference at all. All the snippets behave the same because as per the C standard, a function identifier and a pointer to it, although being of different type, are treated exactly the same. That is, f, *f and &f are interchangeable in your case. There is no run-time difference, and the program will be compiled to use the address of the function in each of the four snippets, so the compiler really does the same thing for each of your snippets.

    See §6.3.2.1 point 4 of the C99 standard (page 46 here):

    A function designator is an expression that has function type. Except when it is the operand of the sizeof operator 54) or the unary & operator, a function designator with type "function returning type" isconverted to an expression that has type "pointer to function returning type".

    54) Because this conversion does not occur, the operand of the sizeof operator remains a function designator and violates the constraint in 6.5.3.4.

  2. The one you like the most, really. It's common to see functions defined to take a pointer to function and then called passing the function name itself. It's a matter of style.

  3. Doesn't make a difference, you can invoke it in any case.

  4. Using sizeof() on a function is forbidden by the standard. It's therefore undefined behavior and the values you see have no meaning.

    See §6.5.3.4 of the C99 standard (page 80 here):

    The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member.

  5. Same as 4.


As per point 1, you can verify by yourself that the compiler always does the exact same thing in all cases by looking at the produced binary using objdump. In all cases, the result is the following on my machine (see my comments marked by <<<):

$ gcc -O1 x.c
$ objdump -Mintel -d a.out

...

0000000000000705 <execute>:
 705:   53                      push   rbx
 706:   48 89 fb                mov    rbx,rdi
 709:   ba 08 00 00 00          mov    edx,0x8    # <<< move func address to rbx
 70e:   48 89 fe                mov    rsi,rdi
 711:   48 8d 3d e3 00 00 00    lea    rdi,[rip+0xe3]        # 7fb <_IO_stdin_used+0xb>
 718:   b8 00 00 00 00          mov    eax,0x0
 71d:   e8 7e fe ff ff          call   5a0 <printf@plt>
 722:   b8 00 00 00 00          mov    eax,0x0
 727:   ff d3                   call   rbx        # <<< call the func by its address
 729:   5b                      pop    rbx
 72a:   c3                      ret

000000000000072b <main>:
 72b:   48 83 ec 08             sub    rsp,0x8
 72f:   ba 01 00 00 00          mov    edx,0x1
 734:   48 8d 35 b5 ff ff ff    lea    rsi,[rip+0xffffffffffffffb5]        # 6f0 <print>
 73b:   48 8d 3d c3 00 00 00    lea    rdi,[rip+0xc3]        # 805 <_IO_stdin_used+0x15>
 742:   b8 00 00 00 00          mov    eax,0x0
 747:   e8 54 fe ff ff          call   5a0 <printf@plt>
 74c:   48 8d 3d 9d ff ff ff    lea    rdi,[rip+0xffffffffffffff9d] # <<< pass the func address
                                                                    #  (rip+...) == rip - 0x4e == 0x753 - 0x4e == 0x705
 753:   e8 ad ff ff ff          call   705 <execute>
 758:   b8 00 00 00 00          mov    eax,0x0
 75d:   48 83 c4 08             add    rsp,0x8
 761:   c3                      ret
 762:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
 769:   00 00 00
 76c:   0f 1f 40 00             nop    DWORD PTR [rax+0x0]

 ...
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • Excelent answer! I just think that your answer to point 5 is incorrect as "Same as 4". Answer 4 mentions an undefined behavior. The case in question 5 seems to be more about `f` being handled as `(*f)` , that is, it is a pointer anyway, so the size is `8`. The case for size being `1` is about the undefined behavior. – Rafael Eyng Jan 30 '20 at 16:43
  • @RafaelEyng it is not "being handled as `(*f)`". The local variable is declared as `void (f)()`, that is a function type, and the behavior is still undefined. You might get an 8 because GCC likes to think of it as a pointer in that particular instance, but it's still wrong. – Marco Bonelli Jan 30 '20 at 16:57
  • @RafaelEyng instead, something like: `void (*f)() = print; sizeof(f);` would be ok. – Marco Bonelli Jan 30 '20 at 16:58
  • "You might get an 8 because GCC likes to think of it as a pointer in that particular instance, but it's still wrong" -> I'm not sure I understand this. So you mean that GCC has implemented it this way, but it goes against what what specified in the standard? So in another compiler I might get `1` instead of `8`? Just to be even clearer, I'm talking about getting the `sizeof` of the `f` param inside the function `void execute(void f())` – Rafael Eyng Jan 30 '20 at 17:02
  • 1
    @RafaelEyng yes, it's undefined behavior, so what happens is compiler implementation dependent. Even then though, the same compiler could give different answers in different contexts, because since it's undefined behavior some compiler assumptions might be violated and that might result in a different output in some scenarios. The type of `f` inside `execute` is `void f()`, therefore a *function type*, and therefore falls exactly into what §6.5.3.4 describes. – Marco Bonelli Jan 30 '20 at 17:09
  • That is messed up. If compiling with `-pedantic-errors`, I get a `invalid application of 'sizeof' to a function type` only for the first print, not for the second. So GCC really seems to think of it as a pointer to a function, even when it should be a function type – Rafael Eyng Jan 30 '20 at 17:12
  • @RafaelEyng well, the "pointer" part is clearly not in the definition. If that was a pointer, the `sizeof` would be valid indeed. Also, `printf("%p", print)` is UB as well. – Marco Bonelli Jan 30 '20 at 17:15
  • Could you fill in your answer the commands that you've used to compile and get the `objdump`? – Rafael Eyng Jan 30 '20 at 18:02
  • 1
    @RafaelEyng sure, done. – Marco Bonelli Jan 30 '20 at 18:04
2

As most of the question have been answered:

Why in the first print of each snippet, the size of the function variable (sizeof(print)) is always 1? 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 used sizeof(&print)).

sizeof(function) is undefined in the C. The result you are receiving (1) is the gcc extension which allows pointer arithmetic on decayed functions (for the address calculations) It does not have any practical use in the portable code development, but it is sometimes handy in the embedded world.

Your printf format string is wrong and printf invokes an UB.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • How exactly is `sizeof` a function handy in embedded? What does it even mean, size of a local function in bytes? Seems like yet another over-engineered GNU bloat feature to me. If you'd actually want to calculate function sizes etc in run-time, you'd rather convert to `uintptr_t` first. And that's only useful when doing special case stuff like copying functions from flash to RAM. – Lundin Jan 30 '20 at 14:16
  • @Lundin sizeof(function) is always one. https://godbolt.org/z/afTnNM – 0___________ Jan 30 '20 at 14:28
  • @Lundin if I want to calculate the size of the function i Would use another way. – 0___________ Jan 30 '20 at 14:29