K&R doesn't go over it, but they use it. I tried seeing how it'd work by writing an example program, but it didn't go so well:
#include <stdio.h>
int bleh (int *);
int main(){
char c = '5';
char *d = &c;
bleh((int *)d);
return 0;
}
int bleh(int *n){
printf("%d bleh\n", *n);
return *n;
}
It compiles, but my print statement spits out garbage variables (they're different every time I call the program). Any ideas?
When thinking about pointers, it helps to draw diagrams. A pointer is an arrow that points to an address in memory, with a label indicating the type of the value. The address indicates where to look and the type indicates what to take. Casting the pointer changes the label on the arrow but not where the arrow points.
d
in main
is a pointer to c
which is of type char
. A char
is one byte of memory, so when d
is dereferenced, you get the value in that one byte of memory. In the diagram below, each cell represents one byte.
-+----+----+----+----+----+----+-
| | c | | | | |
-+----+----+----+----+----+----+-
^~~~
| char
d
When you cast d
to int*
, you're saying that d
really points to an int
value. On most systems today, an int
occupies 4 bytes.
-+----+----+----+----+----+----+-
| | c | ?₁ | ?₂ | ?₃ | |
-+----+----+----+----+----+----+-
^~~~~~~~~~~~~~~~~~~
| int
(int*)d
When you dereference (int*)d
, you get a value that is determined from these four bytes of memory. The value you get depends on what is in these cells marked ?
, and on how an int
is represented in memory.
A PC is little-endian, which means that the value of an int
is calculated this way (assuming that it spans 4 bytes):
* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴
. So you'll see that while the value is garbage, if you print in in hexadecimal (printf("%x\n", *n)
), the last two digits will always be 35
(that's the value of the character '5'
).
Some other systems are big-endian and arrange the bytes in the other direction: * ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃
. On these systems, you'd find that the value always starts with 35
when printed in hexadecimal. Some systems have a size of int
that's different from 4 bytes. A rare few systems arrange int
in different ways but you're extremely unlikely to encounter them.
Depending on your compiler and operating system, you may find that the value is different every time you run the program, or that it's always the same but changes when you make even minor tweaks to the source code.
On some systems, an int
value must be stored in an address that's a multiple of 4 (or 2, or 8). This is called an alignment requirement. Depending on whether the address of c
happens to be properly aligned or not, the program may crash.
In contrast with your program, here's what happens when you have an int
value and take a pointer to it.
int x = 42;
int *p = &x;
-+----+----+----+----+----+----+-
| | x | |
-+----+----+----+----+----+----+-
^~~~~~~~~~~~~~~~~~~
| int
p
The pointer p
points to an int
value. The label on the arrow correctly describes what's in the memory cell, so there are no surprises when dereferencing it.