hama
hama

Reputation: 210

When is typecasting a pointer allowed?

I'm studying for an exam, and one of the sample questions I've been given is the following:

"Which of the following expressions when assigned to new_val (the expression will replace HERE) will print 7?" Choose all that apply:

float m = 7.0
int *a = (int *) &m;

int new_val = HERE;
printf("%d\n", new_val);

(a) *(int *) &m

(b) *a

(c) (int) m

(d) None of the above.


The answer is (c) only.

I've run the program on my own machine, and what I found agrees with the answer. However, I was wondering how someone can reason to get to this answer? More generally, I'm having trouble understanding the rules regarding casting pointers. I've read a couple of other posts on StackOverflow, which weren't too helpful to me.

I was wondering if someone could please help clarify how I can work through questions similar to this one.

Upvotes: 0

Views: 117

Answers (4)

Eric Postpischil
Eric Postpischil

Reputation: 223284

The question is defective. The C standard permits each of (a), (b), and (c) to print “7”, and it also permits each of them not to print “7”.

For (a), we have int new_val = * (int *) &m;. Here, &m has type float *, so this converts float * to int *. C 2018 6.3.2.3 7 tell us we can make this conversion: “A pointer to an object type may be converted to a pointer to a different object type.” But it also “If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.” So, in an implementation where float has a stricter alignment requirement than int, the behavior is not defined by the C standard, so a conforming C implementation is permitted to print “7” and is permitted not to print “7”. Assuming the implementation evaluates the conversion successfully without having an alignment problem, it then applies *. This is covered in the next paragraph.

In (b), we have int new_val = *a;, where a has been initialized to (int *) &m;. As with (a), this expression converts a float * to int * and therefore falls afoul of the alignment problem above. But, again assuming the alignment problem is avoided, it then uses the pointer to access the float object m as if it were an int. This violates C 2018 6.5 7, which states that “An object shall have its stored value accessed only by an lvalue expression that has one of the following types:” and then goes on to list various options, none of which permits accessing a float as an int. Since this “shall” requirement is violated, C 2018 4 1 tells us the behavior is undefined: “If a ‘shall’ or “shall not’ requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined.” Since the C standard does not define the behavior, a conforming implementation is permitted to print “7” and is permitted not to print “7”.

In (c), we have int new_val = (int) m;, where m is a float with the value 7. This is a well-defined conversion, and the result is 7, so printf("%d\n", new_val); would normally print “7” (with a new-line character). However, the earlier problem remains: Even with this proper definition for new_val, the earlier declaration int *a = (int *) &m; performs a conversion that may cause undefined behavior. This can cause undefined behavior before we read the definition of new_val. Therefore, as with (a) and (b), a conforming implementation is permitted to print “7” and is permitted not to print “7”.

Upvotes: 1

Willis Blackburn
Willis Blackburn

Reputation: 8204

Because a = (int *)&m, options (a) and (b) are identical. In both cases you're taking the address of m, casting it to int *, and then dereferencing it.

The reason that (a) and (b) don't work is that &m is a pointer to float (a float *) which is not a pointer to int (int *).

Consider two pointers, one is a pointer to a Window, and another is a pointer to a Joystick. It's pretty clear that you can't mix those up, right? You couldn't pass a pointer-to-Window to a function that expects a pointer-to-Joystick. You could cast the pointer, but that doesn't make the Window into a Joystick.

So pointer to float and pointer to int are just incompatible, and if you cast a pointer to float to a pointer to int and try to dereference it, you'll get garbage.

But (c) works because here you're taking the actual float value m and asking the compiler to convert that value into an int value. The compiler knows how to do this and generates the correct code.

You're probably wondering, if the compiler knows how to convert a float into an int, why can't it convert the pointer to float into a pointer to int? In fact it does; that's the purpose of the cast. But it converts the pointer not the thing the pointer is pointing to.

Upvotes: 0

The way to approach this is to look at each of the multiple choice answers and work out what the meaning of the statement is. Consider the types of the variables.

But in this case, casting a float * to an int * just means you're telling the compiler "you know this memory address that holds a float? Access that memory as if it holds an int". ints and floats are not stored the same way in memory, so trying to access it in the wrong format gives a nonsense result. Casting a pointer does not convert the data which the pointer is pointing to.

In the case of option c, notice that you're not casting a pointer - you're casting a float to an int. This is a straightforward conversion.

Upvotes: 1

jason120
jason120

Reputation: 94

But using

int new_val = (int)m;

ignores everything done in this previous line with int* a = ...

So you are just casting the original float m back to an int.

EDIT try the following:


#include "stdio.h"

int main(int argc, char** argv) {
    float m = 7.1;
    int new_val = (int)m;
    printf("%d\n", new_val);

}

Upvotes: 1

Related Questions