1

I've recently been messing around with pointers and I would like to know a bit more about them, namely how they are organized in memory after using malloc for example.
So this is my understanding of it so far.

int **pointer = NULL;

Since we explicitly set the pointer to NULL it now points to the address 0x00.

Now let's say we do

pointer = malloc(4*sizeof(int*));

Now we have pointer pointing to an address in memory - let's say pointer points to the address 0x0010.

Let's say we then run a loop:

for (i = 0; i<4; i++) pointer[i] = malloc(3*sizeof(int));

Now, this is where it starts getting confusing to me. If we dereference pointer, by doing *pointer what do we get? Do we get pointer[0]? And if so, what is pointer[0]?

Continuing, now supposedly pointer[i] contains stored in it an address. And this is where it really starts confusing me and I will use images to better describe what I think is going on.

In the image you see, if it is correct, is pointer[0] referring to the box that has the address 0x0020 in it? What about pointer[1]?

If I were to print the contents of pointer would it show me 0x0010? What about pointer[0]? Would it show me 0x0020?

Thank you for taking the time to read my question and helping me understand the memory layout.

Pedro
  • 123
  • 4
  • What is you problem? – too honest for this site Apr 15 '17 at 22:00
  • 2
    The diagram looks plausible, but it's showing kind of a special case, where all the allocated blocks are contiguous and returned in increasing order. You should try drawing another one where they are in a different order and have gaps between them. Then put them side by side and see how they represent the same abstract structure. –  Apr 15 '17 at 22:06
  • @Olaf I'm just trying to understand if my understanding of how pointers work is correct. Things can get pretty confusing really fast and a solid base is always good. – Pedro Apr 15 '17 at 22:19
  • @WumpusQ.Wumbley So what you're saying is that pointer[1] could be, for example, 0x00a0? Is there any logic to how malloc decides where the starting address of a block of allocated memory is? Or is it completely random? – Pedro Apr 15 '17 at 22:27

2 Answers2

2

Pointer Refresher

A pointer is just a numeric value that holds the address of a value of type T. This means that T can also be a pointer type, thus creating pointers-to-pointers, pointers-to-pointers-to-pointers, and crazy things like char********** - which is simply a pointer (T*) where T is a pointer to something else (T = E*) where E is a pointer to something else (and so on...).

Something to remember here is that a pointer itself is a value and thus takes space. More specifically, it's (usually) the size of the addressable space the CPU supports.

So for example, the 6502 processor (commonly found in old gaming consoles like the NES and Atari, as well as the Apple II, etc.) could only address 16 bits of memory, and thus its "pointers" were 16-bits in size.

So regardless of the underlying type, a pointer will (usually) be as large as the addressable space.

Keep in mind that a pointer doesn't guarantee that it points to valid memory - it's simply a numeric value that happens to specify a location in memory.

Array Refresher

An array is simply a series of T elements in contiguously addressable memory. The fact it's a "double pointer" (or pointer-to-a-pointer) is innocuous - it is still a regular pointer.

For example, allocating an array of 3 T's will result in a memory block that is 3 * sizeof(T) bytes long.

When you malloc(...) that memory, the pointer returned simply points to the first element.

T *array = malloc(3 * sizeof(T));
printf("%d\n", (&array[0] == &(*array))); // 1 (true)

Keep in mind that the subscript operator (the [...]) is basically just syntactic sugar for:

(*(array + sizeof(*array) * n)) // array[n]

Arrays of Pointers

To sum all of this up, when you do

E **array = malloc(3 * sizeof(E*));

You're doing the same thing as

T *array = malloc(3 * sizeof(T));

where T is really E*.

Two things to remember about malloc(...):

  1. It doesn't initialize the memory with any specific values (use calloc for that)
  2. It's not guaranteed (nor really even common) for the memory to be contiguous or adjacent to the memory returned by a previous call to malloc

Therefore, when you fill the previously created array-of-pointers with subsequent calls to malloc(), they might be in arbitrarily random places in memory.

All you're doing with your first malloc() call is simply creating the block of memory required to store n pointers. That's it.

To answer your questions...

If we dereference pointer, by doing *pointer what do we get? Do we get pointer[0]?

Since pointer is just a int**, and remembering that malloc(...) returns the address of the first byte in the block of memory you allocated, *pointer will indeed evaluate to pointer[0].

And if so, what is pointer[0]?

Again, since pointer as the type int**, then pointer[0] will return a value type of int* with the numeric contents of the first sizeof(int*) bytes in the memory block pointed to by pointer.

If I were to print the contents of pointer would it show me 0x0010?

If by "printing the contents" you mean printf("%p\n", (void*) pointer), then no.

Since you malloc()'d the memory block that pointer points to, pointer itself is just a value with the size of sizeof(int**), and thus will hold the address (as a numeric value) where the block of memory you malloc()'d resides.

So the above printf() call will simply print that value out.

What about pointer[0]?

Again assuming you mean printf("%p\n", (void*) pointer[0]), then you'll get a slightly different output.

Since pointer[0] is the equivalent of *pointer, and thus causes pointer to be dereferenced, you'll get a value of int* and thus the pointer value that is stored in the first element.

You would need to further dereference that pointer to get the numeric value stored in the first integer that you allocated; for example:

printf("%d\n", **pointer);
// or
printf("%d\n", *pointer[0]);
// or even
printf("%d\n", pointer[0][0]); // though this isn't recommended
                               // for readability's sake since
                               // `pointer[0]` isn't an array but
                               // instead a pointer to a single `int`.
Community
  • 1
  • 1
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
  • I know I marked this answer as correct already but I failed to understand why doing printf("%p", (void*)pointer) wouldn't show me, in this case, 0x0010. I mean, what would it show me? – Pedro Apr 17 '17 at 09:39
  • @Pedro Oh, I misread your question. In that particular case, it would. In normal circumstances it would show a random pointer address – Qix - MONICA WAS MISTREATED Apr 17 '17 at 17:47
0

If I dereference pointer, by doing *pointer what do I get? pointer[0]?

Yes.

And if so, what is pointer[0]?

With your definitions: 0x0020.

In the image you see, if it is correct

It seems correct to me.

is pointer[0] referring to the box that has the address 0x0020 in it?

Still yes.

What about pointer[1]?

At this point, I think you can guess that it woud show: 0x002c.

To go further

If you want to check how memory is managed and what pointers look like you can use gdb. It allows running a program step by step and performing various operations such as showing the content of variables. Here is the main page for GNU gdb. A quick internet search should let you find numerous gdb tutorials.

You can also show the address of a pointer in c by using a printf line:

int *plop = NULL;
fprintf(stdout, "%p\n", (void *)pointer);

Note: don't forget to include <stdio.h>

silel
  • 567
  • 2
  • 10
  • 1
    The fprintf invokes undefined behaviour. `%p` expects a `void *`. (unrelated: why not use `printf`??) – too honest for this site Apr 15 '17 at 22:18
  • @Olaf What do you mean by undefined behaviour? It worked fine for me. Maybe trying to print the address of NULL is not the best idea. On my machine it shows (nil). You can try assigning `plop` to an allocated memory zone, it will show its address. – silel Apr 15 '17 at 22:23
  • @silel As in the standard specifies you need to pass a `void*` because not all pointer sizes are the same. – Qix - MONICA WAS MISTREATED Apr 15 '17 at 22:25
  • @silel: You pass a wrong type for this conversion type specifier. What is unclear about undefined behaviour? "the code works" is a non sequitur. – too honest for this site Apr 15 '17 at 22:30
  • Ok so I did some digging and many other posts in SO actually mention the same rule "need to cast to void *" for `%p`. If possible, I would very much like some reference for that. Anything I have searched points back to SO. – silel Apr 15 '17 at 22:34
  • @Olaf why use `fprintf` instead of `printf`. Mostly habit, some say it is also easier to switch from `stdout` to `stderr` this way. – silel Apr 15 '17 at 22:44
  • @Olaf The man page of fprintf states _p The void * pointer argument is printed in hexadecimal (as if by %#x or %#lx)._ but it does not explain __why__ a cast is necessary. I am currently reading the C standard, it may be that I am missing something but I fail to see __why__ the cast is needed. – silel Apr 15 '17 at 23:46
  • @silel: read on, passing the wrong type to a parameter is undefined behaviour is one of the things you should have learned in the first lesson about functions. – too honest for this site Apr 16 '17 at 02:25
  • @Olaf Please back your statements with actual references. [This versions of the standard](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) is what motivates my point. Your last statement is clearly invalidated by section 6.9.1 - point 10 of the linked document. Good day to you. – silel Apr 16 '17 at 09:17
  • @silel: Last post, this is getting nowhere: 1) This is not the standard, but an early draft. Use the final draft as a reference if you can#t/don't want to affort buying the standard document (this is completely acceptable, I can assure there are two unrelated differences only). 2) You violate the effective type rule. This is one of the most important sections of the whole document. Every C programmer should be expected to be at least aware of its basic implications. You are effectively telling it does not matter which type you pass to a function parameter! – too honest for this site Apr 16 '17 at 09:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/141815/discussion-between-silel-and-olaf). – silel Apr 16 '17 at 09:26
  • @silel-- `fprintf()` takes a variable number of arguments, and the arguments that follow the format string can be of _any type_. There is no type specified in the function prototype to convert these arguments. The function itself must use the conversion specifiers from the format string, and in the case of `%p`, `(void *)` is expected. [A mismatch between argument type and conversion specifier leads to undefined behavior](http://port70.net/~nsz/c/c11/n1570.html#7.21.6.1p9). – ad absurdum Apr 16 '17 at 17:05
  • See also [§6.5.2.2 7 of the C11 Draft Standard](http://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p7): "The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments." – ad absurdum Apr 16 '17 at 17:06
  • 1
    @DavidBowling Thank you very much! – silel Apr 16 '17 at 22:29