17

I have recently encountered a behavior in C++ regarding function pointers, that I can't fully understand. I asked Google for help as well as some of my more experienced colleagues, but even they couldn't help.

The following code showcases this mystique behavior:

class MyClass{
private:
    int i;

public:
    MyClass(): i(0) {}
    MyClass(int i): i(i) {}

    void PrintText() const { std::cout << "some text " << std::endl;}
};

typedef void (*MyFunction) (void*);

void func(MyClass& mc){
    mc.PrintText();
}

int main(){    
    void* v_mc = new MyClass;
    MyFunction f = (MyFunction) func; //It works!
    f(v_mc); //It works correctly!!!

    return 0;
}

So, first I define a simple class that will be used later (especially, it's member method PrintText). Then, I define name object void (*) (void*) as MyFunction - a pointer to function that has one void* parameter and doesn't return a value.

After that, I define function func() that accepts a reference to MyClass object and calls its method PrintText.

And finally, magic happens in main function. I dynamically allocate memory for new MyClass object casting the returned pointer to void*. Then, I cast pointer to func() function to MyFunction pointer - I didn't expect this to compile at all but it does.

And finally, I call this new object with a void* argument even though underlying function (func()) accepts reference to MyClass object. And everything works correctly!

I tried compiling this code with both Visual Studio 2010 (Windows) and XCode 5 (OSX) and it works in the same manner - no warnings are reported whatsoever. I imagine the reason why this works is that C++ references are actually implemented as pointers behind the scenes but this is not an explanation.

I hope someone can explain this behavior.

Smi
  • 13,850
  • 9
  • 56
  • 64
dbajgoric
  • 1,447
  • 11
  • 17
  • 1
    Your code contains a bug (dereferencing a pointer that's been cast to point to a different type). Fix the bug and the mystery will go away. Yes, code with bugs in it will not do what you expect. That's why you should fix bugs when you find them. – David Schwartz Oct 22 '14 at 11:28
  • Your cast hides a warning/error. This is what I get `error: invalid conversion from 'void (*)(MyClass&)' to 'MyFunction {aka void (*)(void*)}' [-fpermissive]`. Don't ignore the compiler's diagnostics. –  Oct 22 '14 at 11:32
  • @remyabel Well, the cast is perfectly legal (and so would be a `reinterpret_cast`). – Angew is no longer proud of SO Oct 22 '14 at 11:33
  • All kinds of illegal things appear to "work" in C++. C++ is not type or memory safe. – usr Oct 22 '14 at 17:06
  • @remyabel what are your compilation flags for gcc? there is no warning/error even compiled with `-Wall -Wextra`. and i think `-fpermissive` actually loosen the language rule, right? – Hongxu Chen Oct 23 '14 at 02:11

4 Answers4

32

The formal explanation is simple: undefined behaviour is undefined. When you call a function through a pointer to a different function type, it's undefined behaviour and the program can legally do anything (crash, appear to work, order pizza online ... anyting goes).

You can try reasoning about why the behaviour you're experiencing happens. It's probably a combination of one or more of these factors:

  • Your compiler internally implements references as pointers.
  • On your platform, all pointers have the same size and binary representation.
  • Since PrintText() doesn't access *this at all, the compiler can effectively ignore the value of mc altogether and just call the PrintText() function inside func.

However, you must remember that while you're currently experiencing the behaviour you've described on your current platform, compiler version and under this phase of the moon, this could change at any time for no apparent reason whatsoever (such as a change in surrounding code triggering different optimisations). Remember that undefined behaviour is simply undefined.


As to why you can cast &func to MyFunction - the standard explicitly allows that (with a reinterpret_cast, to which the C-style cast translates in this context). You can legally cast a pointer to function to any other pointer to function type. However, pretty much the only thing you can legally do with it is move it around or cast it back to the original type. As I said above, if you call through a function pointer of the wrong type, it's undefined behaviour.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 11
    mmhm, I wouldn't mind writing some bad code if the compiler could be convinced to order pizza for me.. – Thomas Oct 22 '14 at 14:03
7

I hope someone can explain this behavior.

The behaviour is undefined.

MyFunction f = (MyFunction) func; //It works!

It "works" because you use c-style cast which has the same effect as reinterpret_cast in this case I think. If you had used static_cast or simply not cast at all, the compiler would have warned of your mistake and failed. When you call the wrongly interpreted function pointer, you get undefined behaviour.

eerorika
  • 232,697
  • 12
  • 197
  • 326
2

It's only by chance that it works. Compilers are not guaranteed to make it work. Behind the scenes, your compiler is treating the reference as a pointer, so your alternative function signature just happens to work.

tenfour
  • 36,141
  • 15
  • 83
  • 142
0

I'm sorry, to me isn't clear why you call this a strange behavior, I don't see a undefined behavior that depends on moon cycle here, is the way to use function pointers in C.

Adding some debug output you may see that the pointer to the object remain the same in all the calls.

void PrintText() const { std::cout << "some text " << this << std::endl;}
                                                      ^^^^
void func(MyClass& mc){
    std::cout << (void *)&mc << std::endl;
                         ^^^
void *v_mc = new MyClass;
std::cout << (void *)v_mc << std::endl;
                     ^^^^
Alex
  • 3,264
  • 1
  • 25
  • 40