K
Keith Thompson
Robbie Brown said:Helmut Tessarek said:On 21.01.14 7:33 , Robbie Brown wrote:
check out my mail signature. it will also answer your question.
[...]
/*
Thou shalt not follow the NULL pointer for chaos and madness
await thee at its end.
*/
Good advice, but not actually relevant in this case.
The OP *expected* a segmentation fault on dereferencing a null pointer.
The problem was that the pointer object in question was uninitialized,
and therefore might or might not contain a null pointer value.
Yes, I'm starting to get the impression that, unlike other languages I
have used, C (or rather the C compiler perhaps) doesn't stop you from
doing all manner of exceptionally stupid things.
Yes. Another interesting, um, feature of C is that the syntax is what I
think of as "dense". What that means is that a single-character typo in
an otherwise correct C program can easily produce something that's
perfectly correct as far as the compiler is concerned, but has
completely different behavior.
For example, for no other reason that experimentation I tried to get my
head around pointers to pointers and came up with the following.
Trying hard not to make assumptions, just observations.
[Linux 3.2.0-23-generic x86_64 GNU/Linux]
int **arpi = (int**) malloc(sizeof(int*) * 5);
A good idiom for malloc that mostly avoids type mismatches is:
int **arpi = malloc(5 * sizeof *arpi);
Casting the result of malloc is unnecessary and can mask errors in some
cases. Applying sizeof to *arpi (more generally, to what the LHS points
to) ensures that you have the correct size and type without having to
repeat the type name.
*(arpi + 4) = malloc(sizeof(int));
Probably better written as:
arpi[4] = malloc(sizeof *(arpi[4]));
*(*(arpi + 4)) = 14;
*(arpi[4]) = 14;
If I run this through gdb I can see what I expected to see (there's that
word again, what other word can I use?).
arpi is a pointer to the first of 5 64 bit addresses.
the first 4 addresses contain 0x0000000000000000 I hope I understand
that these are uninitialized addresses ... or maybe they have been
initialized to 0 by some voodoo priest anyway
malloc returns a pointer to uninitialized memory. The contents might
happen to be all bits zero, but that's not guaranteed, and you shouldn't
rely on it. And the null pointer is very commonly represented as
all-bits-zero, but that's not guaranteed either.
the fifth address contains the 64 bit address 0x0000000000602010
this seems reasonable as I malloc'd enough space for a pointer to int.
if I inspect the contents of 0x602010 I see 0x0e which is (I hope) what
I was expecting
Yes.
Then it got all strange again
I changed the first line to
int **arpi = (int**) malloc(sizeof(int) * 5);
now I malloc int instead of int*
Compile, run, inspect, same old results
I think this works because an int is probably 64 bits same as an address
(gross assumption)
Yes, that's the kind of type mismatch that can be avoided by the idiom I
suggested above.
Then it gets weirder
int **arpi = (int**) malloc(0);
Now realistically what should I 'expect' to happen
I sort of expected it not to compile ... wrong, it compiled
I'm not sure why you'd expect it not to compile. malloc is a library
function, not a built-in language feature. It takes an integer argument
(specifically an argument of the unsigned integer type size_t), and
you've called it with an integer value. Even if 0 were not a valid
argument value, it's of the right type (or rather, is implicitly
convertible to the right type), so there's nothing for the compiler to
complain about. The run time behavior may be another matter; as James
Kuyper already explained, the behavior of malloc(0) is
implementation-defined.
I sort of expected it to blow up ... wrong, ran and exited normally
I even found 0x0e lurking about almost where I hoped it would be.
gdb exposed the memory and it was obviously not right but it still ran.
It's likely that malloc(0) allocated some small amount of memory from
the heap (it could have returned a null pointer, but then your program
probably would have crashed). The actual amount of memory allocated for
malloc(N) is likely to be a bit bigger than N, but you can only safely
access the first N bytes (and only if malloc(N) actually succeeded).
But if you try to access memory beyond those first N bytes, you're
*probably* still accessing memory within your program's memory space.
The behavior is undefined, but that doesn't mean it's going to crash;
if you're *unlucky*, it will appear to "work".