Pedro Cunha
Pedro Cunha

Reputation: 31

Issue dereferencing pointers

What is the difference between those two programs:

#include <stdio.h>

int main( void )
{
int* ptr;
*ptr = 600;

printf("ptr = %d\n", *ptr );
return 0;
}

and

#include <stdio.h>

int main( void )
{
int* ptr = (int*) 600;

printf("ptr = %d\n", *ptr );
return 0;
}

Why on the first one I receive ptr = 600, while on the second, I receive an error saying that the program has stopped working?

Upvotes: 0

Views: 59

Answers (3)

e0k
e0k

Reputation: 7161

Both examples fail in their own way

In the first example, you are writing 600 to wherever ptr points, which is undefined because the pointer was never assigned to anything. It may seem to work because it is writing 600 somewhere then reading the value back. You just got lucky that accessing this somewhere didn't cause a segmentation fault.

In the second example, you are explicitly assigning the address 600 to the variable ptr. This is a rather low value for a memory address, which is almost certainly not somewhere in memory you should be trying to read from. When you dereference it (the line with the printf), your program "stopped working" because it attempted to read memory from somewhere it shouldn't.

In short, neither one works. But the way in which the first one fails is a bit more deceptive.


A bit on pointers

Memory is a strange and wonderful thing in that fundamentally everything is just bytes of binary data. When reading anything from memory, a compiler needs some notion of:

  • where to start reading
  • how much to read
  • how to interpret it

The line from your second example int *ptr = (int *)600 declares the variable ptr as a pointer to an int. The pointer ptr allows you to read from arbitrary places in memory when dereferencing it (with *ptr). Notice how the three above requirements are specified. The value of ptr is where to start reading. The other two requirements are given by its type int *, which includes the type of the thing it points to: read sizeof(int) bytes and interpret what is read as a signed integer int.

("How to interpret it" is a bit optional. A void * for example specifies "where to start reading", but explicitly gives no notion of how to interpret what is there, leaving it only as bytes of binary data. Any interface that uses a void * likely has some other mechanism for specifying "how much to read", such as another parameter of type size_t.)

The initialization (int *)600 contains a type cast. This forces the compiler to think of 600 as the memory address where an int resides, making it a value compatible to assign to int *ptr. Hard-coding an address like this is almost certainly asking for trouble, unless you really know what you're doing (have a memory map for the machine you're writing for, etc.) and have a good reason to do so (e.g. memory-mapped I/O, etc.). In short, you are not giving a useful address.

As an analogy, imagine that you need to buy something, and know of a place that sells it. You send your friend out to buy it for you. But your friend is not very bright. He will do exactly as you tell him, but you must give short and simple instructions. You tell him to go to a specific address and buy four of whatever they sell. What could possibly go wrong? In your case, you got your friend arrested for trespassing. These instructions depend entirely on whether or not you provide a correct address. In the second example, he was not allowed to go to address 600, and was stopped trying to get there.

It also depends on whether or not the store you are thinking of even exists. In both of your examples, ptr has a meaningless value. To follow the analogy with your first example, you are not providing any address at all. Your friend instead goes to the last place he was thinking of, gives something (the number 600) to whomever is there, then takes it back. If it worked, it was only because you got lucky about where he went and the person he encountered was cooperative.


Pointers are more useful when they point to things

The way to "fix" your code is to make your pointers actually point to something meaningful. In both examples, you don't actually have a variable of type int that is yours to use anywhere in memory. You could allocate an int as a local variable (on the stack) and have ptr point to it with something like:

int x;          // now x is an int that actually exists
int *ptr = &x;  // initialize ptr to point to x

Then any assignment to *ptr would write to x, for example:

*ptr = 600;  // now x == 600

The more useful thing to do with pointers is to use dynamically allocated heap memory, as in:

int *ptr;
ptr = malloc(sizeof(*ptr));
if ( ptr != NULL ) {
    *ptr = 600;
}

This allocates an int in heap memory, makes ptr point to it, then (if it was successfully allocated) writes 600 to it. A heap allocation like this call to malloc() is what appears to be missing from your first example.

Upvotes: 1

sjsam
sjsam

Reputation: 21965

What is the difference between those two programs:

First piece :

int* ptr; // Uninitialized pointer
*ptr = 600; // Dereferencing causes undefined behaviour

Second piece :

int* ptr = (int*) 600; //600 - A memory location which you may not have access to.
printf("ptr = %d\n", *ptr ); // Dereferencing causes undefined behaviour

Conclusion

There is no difference. Both will crash sooner or later.

Upvotes: 1

Sourav Ghosh
Sourav Ghosh

Reputation: 134346

Both your programs invoke undefined behavior.

  • The first one, writing to an unitialized pointer.

  • The second one, reading from a possibly invalid memory location. FWIW, conversion of an int to a pointer type is also implementation-defined behaviour.

Solution:

Allocate proper memory to ptr before you dereference it.

Upvotes: 1

Related Questions