pointer question

T

the.cool.dude.man

hi,
why doesn't the following program crash.

int main ()
{
char *p;
int x;
p = (char*) malloc(10);
x = (int) p;
free (p);
p = (char *) x;
*p = 1;
}
 
S

Stephen Sprunk

hi,
why doesn't the following program crash.

int main ()
{
char *p;
int x;
p = (char*) malloc(10);
x = (int) p;
free (p);
p = (char *) x;
*p = 1;
}

"Undefined behavior" does not mean "guaranteed to crash"; "undefined"
means anything can happen, including your program apparently working as
you intended. It's not even guaranteed to be consistent; many folks
find that UB only manifests in crashes when they're demoing their
program to customers and/or management.

(In this specific case, it's likely that either free() does not
immediately return memory to the OS thus the processor does not know it
should trap on the access, or the compiler is smart enough to optimize
away the write to *p thus there's nothing to trap. The same code, on a
different implementation, might crash every time.)

S
 
K

Keith Thompson

why doesn't the following program crash.

int main ()
{
char *p;
int x;
p = (char*) malloc(10);
x = (int) p;
free (p);
p = (char *) x;
*p = 1;
}

No particular reason. It could crash on some systems; the fact that
it doesn't do so on yours is just bad luck.

The program exhibits multiple instances of undefined behavior:

It calls malloc with no visible declaration, so the compiler quietly
assumes that it takes an argument of type int and returns a result of
type int (in fact it takes an argument of type size_t and returns a
result of type void*). The bogus int result is then converted to
char*. (Solution: add "#include <stdlib.h>".)

It converts a value from char* to int. This might work on some
systems; on others, it might lose information (say, if int is 32 bits
and char* is 64 bits) or worse.

It converts a value from int to char*. Again, this might work on some
systems, but on others it might not.

It dereferences the resulting pointer and stores an int value in the
location to which it points. If information was lost in the
conversion from char* to int and back to char*, this could attempt to
store the int value 1 at some arbitrary location, with arbitrarily bad
results. Just dereferencing the pointer, if only to examine the
target value without storing anything, would invoke undefined
behavior. For that matter, just examining the pointer value without
dereferencing it could invoke undefined behavior.

Here's a version of your program that avoids *most* of these problems:

#include <stdlib.h>
int main(void)
{
char *p;
p = malloc(10);
if (p == NULL) {
return EXIT_FAILURE;
}
free(p);
*p = 1;
return 0;
}

I think you were converting the pointer value to int and saving it in
a separate object in attempt to "hide" it from the compiler. This
isn't really necessary to illustrate the point; the code above does
the same thing.

Assuming malloc succeeds, p points to the first byte of a newly
allocated 10-byte object. Calling free() causes that object to reach
the end of its lifetime, and invalidates any pointer that points to
it. Note that p is passed to the free() *by value*, as all function
arguments are, so it still contains the same bit pattern that it did
after the successful call to malloc() -- but that bit pattern, rather
than representing a valid pointer value, now represents an invalid
pointer value. The value of p is now indeterminate, and any attempt
to dereference, or even to examine its value, invokes undefined
behavior.

But undefined behavior doesn't necessary cause your program to crash.
In a typical implementation, free() does some bookkeeping that allows
that 10-byte chunk of memory to be available for later allocation --
but the memory is still there, in the same place. You just no longer
"own" it. You can try to modify it, and it's quite likely that you'll
succeed; the C implementation isn't required to do anything to stop
you. It's entirely *your* responsibility to refrain from touching
memory you don't own.

Some languages require run-time checking to catch this kind of error.
C allows compilers to provide such checking, but doesn't require it,
and most C compilers don't provide it. The tendency is to assume that
the programmer knows what he's doing.
 
M

Martin Ambuhl

why doesn't the following program crash.

Because your implementation chose not to crash on some instances of
undefined behavior.
int main ()
{
char *p;
int x;
p = (char*) malloc(10);

This line could well lead to a crash.
Without a declaration for malloc in scope, C assumes that it returns an
int. Of course it doesn't return an int but a void*, and if you had
left out the senseless cast your compiler might have told you so.
As it happens, the guarenteed similarity of void* and char* might have
saved your ass. The language of conversion of pointers to another type

Any pointer type may be converted to an integer type. Except as
previously specified, the
result is implementation ­defined. If the result cannot be
represented in the integer type,
the behavior is undefined. The result need not be in the range of
values of any integer
type.

only guarantees that some integer type (not necessarily an int) can be
the target. If a void* cannot be represented in an int, then the
behavior of the first conversion (void* -> int) is undefined.

And when you have converted some T* to the appropriate integer, the only
kind of conversion back to pointer which is guaranteed to work is to T*.
From the standard:

A pointer to an object or incomplete type may be converted to a
pointer to a different
object or incomplete type. If the resulting pointer is not correctly
aligned for the
pointed­ to type, the behavior is undefined. Otherwise, when
converted back again, the
result shall compare equal to the original pointer. When a pointer to
an object is
converted to a pointer to a character type, the result points to the
lowest addressed byte of
the object. Successive increments of the result, up to the size of
the object, yield pointers
to the remaining bytes of the object.

And you have been lucky with your choice of target, since the standard
tells us

A pointer to void shall have the same representation and alignment
requirements as a
pointer to a character type.

x = (int) p;

You are guaranteed that p can be converted to _some_ integer type, but
not necessarily to an int (see above). Your implementation used a
pointer value for p which could be converted to an int. Next time you
will be luckier and the program will fail.
free (p);
p = (char *) x;

Since you were not guaranteed that the original value of p could be
stored in an int, you cannot be sure that x stores a value which can
point to the same location.

Just because you freed the space previously allocated does not mean that
the implementation must refuse to let you use that space. Or whatever
space p now points to. Just because you do something stupid doesn't
mean that the program _must_ crash, only that it might.
 
J

James Kuyper

hi,
why doesn't the following program crash.

int main ()
{
char *p;
int x;
p = (char*) malloc(10);
x = (int) p;
free (p);
p = (char *) x;
*p = 1;
}

Because the standard imposes no requirements on such code. In
particular, it does not require such code to crash.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,281
Latest member
Pedroaciny

Latest Threads

Top