Reputation: 210
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
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
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
Reputation: 517
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
". int
s and float
s 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
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