In C, if I cast & dereference a pointer, does it matter which one I do first?
Asked Answered
D

6

15

In C, you can cast both simple data types like int, float, and pointers to these.

Now I would have assumed that if you want to convert from a pointer to one type to the value of another type (e.g. from *float to int), the order of casting and dereferencing does not matter. I.e. that for a variable float* pf, you have (int) *pf == *((int*) pf). Sort of like commutativity in mathematics...

However this does not seem to be the case. I wrote a test program:

#include <stdio.h>
int main(int argc, char *argv[]){
  float f = 3.3;
  float* pf = &f;
  int i1 = (int) (*pf);
  int i2 = *((int*) pf);
  printf("1: %d, 2: %d\n", i1, i2);
  return 0;
}

and on my system the output is

1: 3, 2: 1079194419

So casting the pointer seems to work differently from casting the value.

Why is that? Why does the second version not do what I think it should?

And is this platform-dependent, or am I somehow invoking undefined behaviour?

Determiner answered 8/1, 2011 at 14:14 Comment(0)
A
18

The following says get the float at pf and convert it to an integer. The casting here is a request to convert the float into an integer. The compiler produces code to convert the float value to an integer value. (Converting float values to integers is a "normal" thing to do.)

int i1 = (int) (*pf);

The following says first FORCE the compiler to think pf points to an integer (and ignore the fact that pf is a pointer to a float) and then get the integer value (but it isn't an integer value). This is an odd and dangerous thing to do. The casting in this case DISABLES the proper conversion. The compiler performs a simple copy of the bits at the memory (producing trash). (And there could be memory alignment issues too!)

int i2 = *((int*) pf);

In the second statement, you are not "converting" pointers. You are telling the compiler what the memory points to (which, in this example, is wrong).

These two statements are doing very different things!

Keep in mind that c some times uses the same syntax to describe different operations.

=============

Note that double is the default floating point type in C (the math library typically uses double arguments).

Alodi answered 8/1, 2011 at 23:57 Comment(0)
T
5

If you dereference first, cast to int later, you will get the usual (truncation) behavior of casts from float to int. If you cast to pointer to int first, then dereference, the behaviour isn't defined by the standard. Usually it manifests in interpreting the memory that contains the float as int. See http://en.wikipedia.org/wiki/IEEE_754-2008 for how that works out.

Tabber answered 8/1, 2011 at 14:17 Comment(5)
Usually it manifests as a "strict aliasing" violation that results in reading the wrong data or no data at all, at least on modern gcc.Sitdown
It doesn't manifest in, it is a violation, and yes, gcc usually warns you about it (it doesn't catch all cases, afaik). I said it isn't defined, I just explained what happens in his case - you can easily check that, e.g. with a small ruby snippet [3.3].pack("f").unpack("I") => [1079194419]Tabber
Just for information, my example program above compiles without warnings under -Wall, so yes, gcc does not catch all cases ;-).Determiner
See ideone.com/qGW9C for example warnings :) To quote an IRC bot, -Wall is only -Wsome; it leaves out many warnings. To really get a lot of warnings, you should use '-Wall -Wextra -std=c++98 -pedantic' or '-Wall -Wextra -std=c++0x -pedantic'Tabber
only that this is C, here, and you should use -std=c99, or so.Chrissie
W
4

Of course it does! Casting tells the compiler how to look at the some section of memory. When you then access the memory, it tries to interpret the data there based on how you told it look at it. It's easier to understand it using the following example.

int main()
{
    char A[] = {0, 0, 0, 1 };
    int p = *((int*)A);
    int i = (int)*A;
    printf("%d %d\n", i, p);
    return 0; 
}

The output on a 32-bit little endian machine will be 0 16777216. This is because (int*)A tells the compiler to treat A as a pointer to integer and hence when you dereference A, it looks at the 4 bytes starting from A (as sizeof(int) == 4). After accounting for the endian-ness, the content of the 4 bytes evaluated to 16777216. On the other hand, *A dereferences A to get 0 and (int)*A casts that to get 0.

Wicker answered 8/1, 2011 at 14:31 Comment(2)
Thanks for the example. BTW, I believe there isn't even a guarantee that int is 32 bit, it could be 16bit. With a 16bit int your example should give 256(=2^8) on little-endian architectures, shouldn't it?Determiner
Had assumed 32-bit integer. Will update answer to make that explicit. If int is 16-bits (2 bytes), only two bytes (A[0], A[1]) will be used. As A[0] = A[1] = 0, the integer value will be 0.Wicker
M
2

Cast, then dereference means "pretend I am pointing to this other thing, and then get the other thing that is supposedly being pointed at".

Dereference, then cast means "get the thing I am actually pointing at, and then convert it to this other thing by the usual rules".

"The usual rules" can change the bits around in order to get a value of another type that logically represents the same value. Pretending you're pointing at something you're not actually pointing at cannot.

Mccahill answered 8/1, 2011 at 14:57 Comment(0)
P
1

An int is not represented in memory the same as a float. What you are seeing is the compiler thinking the pointer is to an int and it looks at the 32 bits of memory thinking it is going to find an int when it is in fact finding an undefined portion of the float, which comes out to some very large number.

Picky answered 8/1, 2011 at 14:17 Comment(0)
P
1

According to 6.5/7 of the standard, roughly speaking, stored value has to be compatible with the effective type or a character type. So, I think the statement int i2 = *((int*) pf) isn't well-defined.

Provolone answered 8/1, 2011 at 19:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.