0

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?

lightning_missile
  • 2,821
  • 5
  • 30
  • 58
  • Imagine `int foo(int *p){ *p = 10; return 0; } f(foo);`. – T.C. Oct 06 '14 at 16:13
  • 2
    this book was written by a C programmer that is using C++ in the spare time, leave it on the shelf . this is basically undefined behaviour . – user2485710 Oct 06 '14 at 16:15
  • @user2485710 this is really a bad code. Bjarne Stroustrup said so. He just used this as an example and I want to know how it works. – lightning_missile Oct 06 '14 at 16:18
  • 1
    @user2485710 As the book is written by Bjarne Stroustrup, the creator of C++, I doubt that your statement "written by a C programmer that is using C++ in the spare time" holds :) – Ferdinand Beyer Oct 06 '14 at 16:26
  • @FerdinandBeyer maybe it's just deliberated example of terrible code, doesn't matter who wrote that, it's an horrible piece of code . – user2485710 Oct 06 '14 at 16:43
  • @user2485710 it's not meant to be valid code, it's an example on how not to do something. Reade the quote by the author – lightning_missile Oct 06 '14 at 16:47

3 Answers3

2

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.

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • `so it is unclear what intPtr will be.` it's undefined behaviour, that's what it is; this is the core of the problem if you are a C++ programmer, you get UB and that's the only thing you should care about . Aside from the obvious fact that this kind of _programming style_ reflects C more than C++ . – user2485710 Oct 06 '14 at 16:20
  • 2
    @user2485710 nobody says that the code supplied in the question is valid or recommended. The question is about *why* this is dangerous, and I feel that "because it is undefined behavior" is not a satisfying answer. – Ferdinand Beyer Oct 06 '14 at 16:24
  • 1
    @Ferdinand Beyer So due to casting, the type is now P2, but it's still pointing to foo which will have different type from P2. The behavior will not be undefined if you point P2 to something with the same type as P2. Is that correct? – lightning_missile Oct 06 '14 at 16:41
  • 2
    I assume you mean "point `pf2` (the variable of type `P2`) to something with the same type", then yes, this is correct: `P2 x; P2 y == reinterpret_cast(x);` is not undefined behavior. But the `reinterpret_cast` is obviously totally unnecessary here. That's basically what the book is saying: Beware of `reinterpret_cast`, that can be used for dangerous things. – Ferdinand Beyer Oct 06 '14 at 16:47
0

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.

antlersoft
  • 14,636
  • 4
  • 35
  • 55
0

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.

Community
  • 1
  • 1
IdeaHat
  • 7,641
  • 1
  • 22
  • 53