2

How is the following function pointer assignment:

exit = (void (*)()) &jump;

Different from:

exit = &jump;

where exit is a function pointer defined as:

void (*exit) ();

and 'jump' is a function declared as:

void jump();
2
  • 2
    What is exit and what is jump? Commented Jul 20, 2015 at 19:20
  • @VladfromMoscow: Thanks! Commented Jul 20, 2015 at 19:24

3 Answers 3

9
exit = (void (*)()) &jump;

The first one uses forced casting. This is dangerous because it can screw up your program without letting you know if the types do not match up, as it will not be caught at compile time.

So if you were to do this:

int func() {return 1;}
auto exit = (void (*)()) &func;

...that would be bad. However, it's better if you do the below:

exit = &jump;

The second one uses compile-time type checking. This is safer because the compiler will check for type at compile-time. This gives you a stronger type guarantee.

The best option is to use static_cast<void(*)()> It's type-safe, and tells the programmer more of your intent of what you were trying to do.

auto exit = static_cast<void(*)()>(&func);

If you have C++, you can try the following things:

If you don't want to concern yourself with type, use auto.

auto exit = &func

This will ensure types will match up. If you want to examine the type, use typeid().name from <typeinfo> which exists in C++11 and later:

std::cout << typeid(exit).name << std::endl;

While this usually gives some weird output for functions, you may find some information that can help you, the best one being if the two types are different.

Here's a working example showing the issue at run-time with function pointer casting: http://coliru.stacked-crooked.com/a/47f74e8b6f389812

Sign up to request clarification or add additional context in comments.

5 Comments

Your first and second points can be mitigated by the reader learning how to use proper C++ casts. Attempting to static_cast the first would fail if detected as invalid by the compiler; if applied to the second, it would ensure this and also signal intent by the programmer. C-style casts should be avoided wherever possible. (So should any cast, but C++ provides a much better set.)
@underscore_d We shouldn't limit ourselves to C++. The question is marked both.
Fair point, but still, by the same token, neither should we limit ourselves to C! My comment provides an option for the C++ side that was not featured in your answer.
Yes, but auto neither signals intent nor prevents the programmer accidentally declaring/assigning the wrong type. Overhead is not the only issue that has to be considered here - as your answer acknowledged.
@VermillionAzure temporary variables of non-trivial type are routinely elided in C++, even if there are theoretical side effects. Temporary variables of "trivial copyable" type can even be elided under the as-if rule. There will be zero overhead in any acceptable compiler's optimized builds from the temporary unnamed variable created by the static cast. The auto case stores a copy, and if jump is not of pointer-to-function or function-type, it could have a cost.
3

They are very similar.

In C, if the functions and types are EXACTLY as described:

void (*exit) ();
void jump();
exit = (void (*)()) &jump;
exit = &jump;

are identical.

Now what happens when I change jump:

int jump();

now we are running undefined behavior, and no errors or warnings occur, if we call exit after:

exit = (void (*)()) &jump;

but here we get a type error:

exit = &jump;

In addition, more subtle differences can occur -- for example jump could get a calling convention, and thus not be able to be stored in exit, and things break.

In C++, there is another subtle difference. If jump is an overloaded function, and exit is not a raw function pointer (but, say, a std::function<void()>), the first could compile while the second might not. Casting a name to a function pointer type can trigger overload resolution, and without it some code fails to work.

You would be safer using static_cast<void(*)()> however.

4 Comments

What you write is true, but practically speaking, I know of no hardware architecture where calling a function returning int through a void (*)() could cause problems. There are some low-level tasks where one has to do things like this.
@AndreyChernyakhovskiy nod, you'd need a callee clean-up with a stack-based return value (instead of a register) for an int. Or maybe a caller clean-up, where the called code expects some room to stuff the int, and it isn't planned (as it is supposed to return void). The fact is that most int sized return values show up in registers, usually in registers the function is not supposed to preserve, makes bad behavior unlikely.
@AndreyChernyakhovskiy the compiler could see that a void function does not return a value, and so use the register normally used for returning ints to hold a value that is not meant to be disturbed by the function call
@matt except a quick survey of calling conventions has return values in registers that functions are not supposed to preserve anyhow. There could be a calling convention that differs, but I have not seen it.
0

The two assignments have the same effect. The first way "casts" &jump to a pointer to a function that takes no parameters and returns void, but &jump already is that!

3 Comments

They're not the same in most cases, and although the example doesn't result in different outputs, it's important to highlight the fallacy of using the two interchangeably.
@VermillionAzure Relative to the code example from the question they are the same.
@VermillionAzure OK, fair enough, thanks. Your answer is much more comprehensive than mine anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.