lightning_missile
lightning_missile

Reputation: 2992

Casting pointer to function types

So in "the c++ programming language, 4th edition", there's a paragraph I don't understand about conversion of pointer-to-function types. Here is some of the code sample.

using P1 = int(*)(int*);
using P2 = void(*)(void);

void f(P1 pf) {
     P2 pf2 = reinterpret_cast<P2>(pf);
     pf2();                                  // likely serious problem
     // other codes
}

When I run this it crashed.

I'm not sure if I am right, but I initially think the "likely serious problem" comment is when pf got casted to P2 in pf2, I think pf2 is not pointing to anything? Because when I created a function that matches P2's type and point pf2 to it, it didn't crash and runs normally.

After the code, I read this:

We need the nastiest of casts, reinterpret_cast, to do conversion of pointer-to-function types. The reason is that the result of using a pointer to function of the wrong type is so unpredictable and system-dependent. For example, in the example above, the called function may write to the object pointed to by its argument, but the call pf2() didn’t supply any argument!

Now I'm completely lost starting from "For example, in the example above" part:

  1. "may write to the object pointed to by its argument" //what object is it exactly?
  2. "but the call pf2() didn’t supply any argument!" //"using P2 = void(*)(void);" doesn't really need an arguement does it?

I think I'm missing something here. Can someone explain this?

Upvotes: 0

Views: 100

Answers (3)

IdeaHat
IdeaHat

Reputation: 7881

This does fail, but not necessarily in the way one might expect.

The implementation of a function pointer is left up to the compiler (undefined). Even the size of a function pointer can be bigger than a void*.

What is guaranteed about the size of a function pointer?

There is no guarentees about anything in the value of the function pointer. In fact, the only even guarentee that the comparison operators will work between function pointers of the same type.

Comparing function pointers

The standard does provide that function pointers can store the values of other function types.

Casting the function pointer to another type undefined behavior, meaning the compiler can do whatever it wants. Whether or not you supply the argument really doesn't matter, and how that would fail depends on the calling convention of the system. As far as your concerned, it could allow "demons to fly out of your nose".

Casting a function pointer to another type

So that brings us back to the statement by the author:

We need the nastiest of casts, reinterpret_cast, to do conversion of pointer-to-function types. The reason is that the result of using a pointer to function of the wrong type is so unpredictable and system-dependent. For example, in the example above, the called function may write to the object pointed to by its argument, but the call pf2() didn’t supply any argument!

That is trying to make the point that with no argument specified, if the function writes the output, it will write to some uninitialized state. Basically, if you look at the function as

int foo(int* arg) {*arg=10;}

if you didn't initialize arg, the author says you could be writing anywhere. But again, there is no guarentee that this even matters. The system could store functions with the footprint int (*)(int*) and void(*)(void) in completely different memory space, in which case instead of the above problem you'd have a jump into a random location in the program. Undefined behavior is just that: undefined.

Just don't do it man.

Upvotes: 0

Ferdinand Beyer
Ferdinand Beyer

Reputation: 67137

For example, in the example above, the called function may write to the object pointed to by its argument (...)

pf is a pointer to a function like this:

int foo(int* intPtr)
{
    // ...
}

So it could be implemented to write to its argument:

int foo(int* intPtr)
{
    *intPtr = 42; // writing to the address given as argument
    return 0;
}

(...) but the call pf2() didn’t supply any argument!

When you call foo through its cast to type P2, it will be called without arguments, so it is unclear what intPtr will be:

P2 pf2 = reinterpret_cast<P2>(pf);

pf2(); // no argument given here, although pf2 really is foo() and expects one!

Writing to it will most likely corrupt something.


Moreover, compilers usually implement calls to functions that return something by reserving space for the return value first, that will then be filled by the function call. When you call a P1 using the signature of P2, the call to P2 won't reserve space (as the return value is void) and the actual call will write an int somewhere it should not, which is another source for corruption.

Upvotes: 2

antlersoft
antlersoft

Reputation: 14786

Now I'm completely lost starting from "For example, in the example above" part:

"may write to the object pointed to by its argument" //what object is it exactly?

P1 is a function expecting a non-const pointer-to-int argument. That means it very well may write to the int referenced in its argument.

"but the call pf2() didn’t supply any argument!" //"using P2 = void(*)(void);" doesn't really need an arguement does it?

When you call the function through another function pointer type passing no argument, the expectations of the called function aren't met. It may try to interpret whatever is on the stack as an int pointer and write to it, causing undefined behavior.

Upvotes: 0

Related Questions