32

I have just read an interesting question here that makes me wonder about two more things:

  1. Why should anyone compare function pointers, given that by conception, functions uniqueness is ensured by their different names?
  2. Does the compiler see function pointers as special pointers? I mean does it see them like, let's say, pointers to void * or does it hold richer information (like return type, number of arguments and arguments types?)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Benjamin Barrois
  • 2,566
  • 13
  • 30

7 Answers7

36

Why should anyone compare function pointers? Here's one example:

#include <stdbool.h>

/*
 * Register a function to be executed on event. A function may only be registered once.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful registration, false if the function is already registered.
 */
bool register_function_for_event(void (*arg)(void));

/*
 * Un-register a function previously registered for execution on event.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful un-registration, false if the function was not registered.
 */
bool unregister_function_for_event(void (*arg)(void));

The body of register_function_for_event only sees arg. It doesn't see any function name. It must compare function pointers to report someone is registering the same function twice.

And if you want to support something like unregister_function_for_event to complement the above, the only information you have is the function address. So you again would need to pass it in, and compare against it, to allow removal.

As for the richer information, yes. When the function type contains a prototype, it's part of the static type information. Mind you that in C a function pointer can be declared without a prototype, but that is an obsolescent feature.

user541686
  • 205,094
  • 128
  • 528
  • 886
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
19
  1. Why would someone compare pointers? Consider the following scenario -

    You have an array of function pointers, say it is a call back chain and you need to call each one of them. The list is terminated with a NULL (or sentinel) function pointer. You need to compare if you have reached the end of the list by comparing with this sentinel pointer. Also, this case justifies previous OPs concern that different functions should have different pointers even if they are similar.

  2. Does the compiler see them differently? Yes. The type information includes all the information about the arguments and the return type.

    For example, the following code will/should be rejected by the compiler -

    void foo(int a);
    void (*bar)(long) = foo; // Without an explicit cast
    
Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • Nice, "callback chains" is probably the shortest possible answer. +1 – user541686 Jan 29 '18 at 07:23
  • 1
    What do you mean, you can't have a null function pointer? You can totally have a null function pointer. Function pointers with static storage duration and no explicit initializer are even initialized to null pointers by default. – user2357112 Jan 29 '18 at 20:21
  • @user2357112 I imagine it means that a function pointer gotten by referencing an actual function can never be null -- so if you have a `void foo() {}`, `foo != NULL`. – Nic Jan 29 '18 at 20:54
  • 3
    @NicHartley: The answer makes the claim in the context of terminating an array of function pointers, though. In that context, a null function pointer would be the natural choice (and this would of course be expressed as `0` or `NULL` rather than trying to take the address of an actual function). – user2357112 Jan 29 '18 at 21:33
  • I find the explanation here rather weak, although that's mostly the questions fault for not including more context. The referenced question deals with non-standard COMDAT folding which would *not* be a problem when comparing against the `nullptr`. The problem only arises if we compare two actual functions against each other. – Voo Jan 29 '18 at 22:07
  • @user2357112 I am unsure on the part if one can cast `NULL` to a function pointer type? Since it is a data pointer, or can I cast `0` to a function pointer type. If it is possible, I will edit the answer accordingly. – Ajay Brahmakshatriya Jan 30 '18 at 03:42
  • NULL is not a data pointer. NULL is a null pointer constant. It can be converted to a null pointer of any pointer type. – user2357112 Jan 30 '18 at 03:44
  • @user2357112 I see, thanks for the clarification. I have updated my answer accordingly. – Ajay Brahmakshatriya Jan 30 '18 at 03:45
  • @Voo, I was unsure if a `nullptr` can be casted to a function pointer. It was clarified now and hence I have corrected that part of my answer. – Ajay Brahmakshatriya Jan 30 '18 at 03:48
18
  1. Why should anyone compare function pointers, given that by conception, functions uniqueness is ensured by their different names?

A function pointer can point to different functions at different times in a program.

If you have a variable such as

void (*fptr)(int);

it can point to any function that accepts an int as input and returns void.

Let's say you have:

void function1(int)
{
}

void function2(int)
{
}

You can use:

fptr = function1;
foo(fptr);

or:

fptr = function2;
foo(fptr);

You might want to do different things in foo depending on whether fptr points to one function or another. Hence, the need for:

if ( fptr == function1 )
{
    // Do stuff 1
}
else
{
    // Do stuff 2
}
  1. Does the compiler see function pointers as special pointers? I mean does it see them like, let's say, pointers to void * or does it hold richer information (like return type, number of arguments and arguments types?)

Yes, function pointers are special pointers, different from pointers that point to objects.

The type of a function pointer has all that information at compile time. Hence, give a function pointer, the compiler will have all that information - the return type, the number of arguments and their types.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    Especially the size of a function pointer can be different, most likely on harvard architectures. The function pointer (value) itself doesn't carry the mentioned information, it is the type that does. – PlasmaHH Jan 29 '18 at 11:11
  • 3
    I second what @PlasmaHH said, there are CPU architectures out there where function pointers are larger than a `void*`: More specifically, function pointers on the PPC are actually pairs of pointers, one points to the code, and the other one points to a global reference table that depends on the executable/shared library that the function was loaded from. Thus, it is not possible to cast a function pointer to `void*` and back on PPC. – cmaster - reinstate monica Jan 29 '18 at 12:52
5

The classical part about function pointers is already discussed in other's answer:

  • Like other pointers, pointers to function can point to different objects at different time so comparing them can make sense.
  • Pointers to function are special and should not be stored in other pointer types (not even void * and even in C language).
  • The rich part (function signature) is stored in the function type - the reason for the above sentence.

But C has a (legacy) function declaration mode. In addition to the full prototype mode that declares the return type and the type for all parameters, C can use the so called parameter list mode which is the old K&R mode. In this mode, the declaration only declares the return type:

int (*fptr)();

In C it declares a pointer to function returning an int and accepting arbitrary parameters. Simply it will be undefined behaviour (UB) to use it with a wrong parameter list.

So this is legal C code:

#include <stdio.h>
#include <string.h>

int add2(int a, int b) {
    return a + b;
}
int add3(int a, int b, int c) {
    return a + b + c;
}

int(*fptr)();
int main() {
    fptr = add2;
    printf("%d\n", fptr(1, 2));
    fptr = add3;
    printf("%d\n", fptr(1, 2, 3));
    /* fprintf("%d\n", fptr(1, 2)); Would be UB */
    return 0;
}

Don't pretend I have advised you to do so! It is now considered as an obsolescent feature and should be avoided. I am just warning you against it. IMHO it could only have some exceptional acceptable use cases.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I didn't want to go into length about this feature, since it's obsolescent, but it's a great rundown you did. +1 – StoryTeller - Unslander Monica Jan 29 '18 at 07:21
  • I had no idea this code was legal. Would it work if you replaced the `int` in `add2` and `add3` arguments by `long`? What I mean is: when you call `fptr(1,2)`, does it blindly send 1 and 2 as int, which is convenient in your example above, or is it parameter-type-aware? – Benjamin Barrois Jan 29 '18 at 07:23
  • @BenjaminBarrois - `1` and `2` are constants of type `int`, always. You can try with `long` or `long long` and see what happens. It should become clear to you why this feature is considered for removal. – StoryTeller - Unslander Monica Jan 29 '18 at 07:26
  • Yes that is why I asked, I wondered if in your example worked because of an unexpected omniscience of the compiler. Without even testing I guess what I'd get, now ;-) – Benjamin Barrois Jan 29 '18 at 07:28
  • @BenjaminBarrois: That's one of the reason why prototypes were invented: when the declaration does not give arguments prototype, the compiler blindly accepts the type of the parameters and does not convert them apart from: integer types of rank less than int (char, short, bit fields) are promoted to int, and float are changed to double. It used to lead to weird error in K&R time... – Serge Ballesta Jan 29 '18 at 08:20
  • For example if you pass a long long when the function expects an int, common implementations only use half of the long long **and use the other half for next parameter**. As I have already said, very fun to debug... – Serge Ballesta Jan 29 '18 at 08:23
  • I believe that most compiler treats `int (*)()` as a distinct type different from `int(*)(int, int)`. So I'm not convinced this is valid standard C even in C89. Now of course in practice, pointers are just addresses so if you are certain of the underlying calling convention, you can cast wildly even between different function pointer types and it will work in practice. – Lundin Jan 29 '18 at 10:22
  • 2
    @Lundin - [6.3.2.3](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3) indicates the conversion is valid. And [6.7.6.3p15](http://port70.net/~nsz/c/c11/n1570.html#6.7.6.3p15) indicates the pointer types are vacuously compatible (since one doesn't have a parameter type list). So it's all valid and dangerous C. – StoryTeller - Unslander Monica Jan 29 '18 at 11:27
2

Imagine how you would implement functionality similar to that of WNDCLASS?

It has a lpszClassName to distinguish window classes from each other, but let's say you didn't need (or didn't have) a string available to distinguish different classes from each other.

What you do have is the window class procedure lpfnWndProc (of type WindowProc).

So now what would you do if someone calls RegisterClass twice with the same lpfnWndProc?
You need to detect re-registrations of the same class somehow and return an error.

That's one case when the logical thing to do is to compare the callback functions.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    You know, this would have been a *great* example 15 years ago. But I don't think many new programmers these days are seeing much exposure to the win32 api... everything is geared around *much* higher level stuff, these days. – Jules Jan 29 '18 at 21:46
  • 1
    @Jules: I guess that's why I said "functionality similar to that of `WNDCLASS`" rather than "`WNDCLASS`". To be honest I'm not sure C *itself* is the epitome of modern abstraction in the first place... – user541686 Jan 29 '18 at 21:55
2

1) There are lots of situations. Take for example the typical implementation of a finite state machine:

typedef void state_func_t (void);

const state_func_t* STATE_MACHINE[] =
{
  state_init,
  state_something,
  state_something_else
};

...

for(;;)
{
  STATE_MACHINE[state]();
}

You might need to include some extra code in the caller for a specific situation:

if(STATE_MACHINE[state] == state_something)
{
  print_debug_stuff();
}

2) Yes the C compiler sees them as distinct types. In fact function pointers have stricter type safety than other types of C, because they cannot get implicitly converted to/from void*, like pointers to object types can. (C11 6.3.2.3/1). Nor can they be explicitly cast to/from void* - doing so would invoke non-standard extensions.

Everything in the function pointer matters to determine its type: the type of the return value, the type of the parameters and the number of parameters. All of these must match, or two function pointers are not compatible.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    The problem with using an FSM as an example is the list of possible transitions is known at compile-time, so you would/should be using a `switch` on an `enum` rather than a list of function pointers. – user541686 Jan 29 '18 at 20:13
  • " list of possible transitions is known at compile-time"-> You wouldnt need to know it if you have a proper Unit/Module Testing setup in place. Such a FSM is easy to maintain in my opinion.You could make "state" in the above code as a enum and check against the last enum value or greater than the last enum value always. That was not the scope of the question anyways :p – AlphaGoku Jan 30 '18 at 05:53
  • @Mehrdad Nonsense. A common compiler optimization of a switch statement is in fact to replace it with an array of function pointers behind the lines. Whether or not that is possible to inline is another story, but a function call may still be much faster than a whole lot of branches. – Lundin Jan 30 '18 at 07:32
  • @AkshayImmanuelD: I missed your comment, sorry. I wasn't saying you "need" to know it at compile-time, and I don't get what unit-testing has to do with what I said. What I was saying was that it's not a compelling answer to this question because one can easily argue that you should be doing it differently. On the other hand, for example, the `register_function_for_event`/`unregister_function_for_event` is pretty compelling, since to remove a function pointer from a list you *have* to do some equality comparisons. – user541686 Jan 30 '18 at 08:53
  • @Mehrdad That's not quite an anti-pattern, but it's certainly not such a good plan. As Lundin says, the fastest code uses an array of function pointers. But there's another issue too, which is cross-coupling. If you need to add new states, using switch statements needs *every* relevant switch statement to be updated. If you only have to add the function pointers to the FSM configuration array, and the FSM architecture ensures the right functions for the state are called at the right time, you get more modular code. – Graham Jan 30 '18 at 11:50
  • @Graham: You guys are completely missing my point. I'm questioning whether the answer is a good answer to **this** question. Please read my reply to AkshayImmanuelD above? – user541686 Jan 30 '18 at 18:33
  • @Mehrdad I read it, but your objection still seemed to be an argument against arrays of function pointers in an FSM implementation. Trying to reverse-engineer your objection: Are you saying that in a FSM you should just be comparing against the state variable, not comparing the function pointers for the state actions? and hence the OP's question about comparing function pointers is not really relevant for the example? If so, I completely agree. But if so, then several people failing to understand is a clue it could have been clearer. :) If you meant something else, please could you clarify? – Graham Jan 31 '18 at 10:09
  • @Graham: No, that's not quite what I'm saying. I'll try to give an analogy. Imagine if the question had been, *"Why would anyone ever use a fork to eat?"* This answer is akin to responding with, *"Well, forks are useful when you're eating a pizza."* I mean, OK, maybe it's true, and there [**are**](https://twitter.com/NowThisGIF/status/715246744011980804) people who prefer to eat pizza with a fork. But it's not exactly a convincing answer to the question!!! Lots of people (rightly or wrongly) would argue you *shouldn't* have to do this, and nobody would invent forks just for this. Same here. – user541686 Jan 31 '18 at 11:00
  • @Mehrdad You're still not helping, I'm afraid. If an answer to your pizza question was "For one example, it would stop you burning your fingers on hot pizza cheese", that's a good answer. It doesn't cover every case, but it's only an *example* which can be generalised to other similar situations. So long as it's valid (and it would be; Italians always eat pizza with cutlery!) then it's a good example. If you're objecting to the FSM example because it's not a valid scenario (like I said), then OK. If you're objecting because it doesn't cover every possible scenario, that's not sensible. – Graham Jan 31 '18 at 13:20
  • @Mehrdad If you can explain your objection using the original question and the original answer, not with an irrelevant analogy, maybe we can see if we agree with you. I thought I understood your objection, because a FSM would not usually be doing a function pointer comparison and hence the example in the answer is not really relevant to the question. But now you say that's not it? Without wanting to be rude, I have to say you're failing to communicate here. – Graham Jan 31 '18 at 13:28
  • @Graham: There's nothing rude about telling people they're failing to communicate. What is rude is simultaneously declaring that their analogy (to their *own* point! that they're purportedly failing to communicate!) is "irrelevant". If you don't understand it, then you hold off on the judgment. If you do understand it, then you can also go ahead and provide yourself with a(nother) explanation and then just be honest and say you understand but you disagree. You don't have to go out of your way to lie, waste their time, and pretend you're not understanding something you dislike or disagree with. – user541686 Jan 31 '18 at 16:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164281/discussion-between-graham-and-mehrdad). – Graham Jan 31 '18 at 16:21
  • @Graham: Let's not, I have no interest in continuing. Take away what you will (or won't). There's no obligation for everyone to agree. – user541686 Jan 31 '18 at 16:23
  • @Mehrdad I've no objection to disagreement, but simply saying "no it isn't" repeatedly does not constitute a discussion. Since you're not interested in a discussion, count me out too. – Graham Jan 31 '18 at 16:39
1

Function pointers are variables. Why should anyone compare variables, given that by concept, variables uniqueness is ensured by their different names? Well, sometimes two variables can have the same value and you want to find out whether it's the case.

C considers pointers to functions with the same argument list and return value to be of the same type.

Dmitry Grigoryev
  • 3,156
  • 1
  • 25
  • 53