Pointers are relatively straight forward, but in your case it looks like you are having difficulty understanding "What is my pointer?" Reading between the tea leaves a bit further, it looks like your primary confusion surround "What do it do with the '*'
s?"
Let's take a step back, and recall that a pointer is simply a normal variable that holds the address of something else as its value. (i.e. it points to where something else is stored in memory). While you normally think of int a = 5;
where a
itself holds 5
as its value, a pointer, int *b = &a;
holds (points to) the address of a
as its value (i.e. b
points to the address in memory where 5
is stored).
Now let's think about how we keep the declarations and use of pointers straight. When you declare a pointer, e.g. char **p2;
, you have declared a pointer-to-pointer-to char
. So when you think about what it is you can assign to p2
, you simply answer the questions (1) What is my pointer? (p2
is a pointer-to: what? pointer-to char
) and (2) What type of address does it store as its value? (p2
holds a pointer-to char
) as its value.
When you declared p2
and attempted to allocate storage, what type did you use? sizeof(char)
. It should be clear now that p2
doesn't hold the address of char
as its value, it holds the address of a pointer-to char
as its value, e.g. (char*)
.
The way to get this right every time is to use the dereferenced pointer as your type-size rather than trying to use a basic type char, short, int, etc..
and then add '*'
s to it hoping to get it right. Since you both declare and initialize with a call to malloc
in one-shot, you mask the simplicity by having the **
part of the declaration in the same line as your allocation. Instead think of it this way to sort it out in your mind to begin with.
char **p2; /* declare a pointer-to-pointer-char */
p2 = malloc (sizeof *p2 * 2); /* use the dereferenced pointer to set typesize */
p2
is your pointer, so *p2
sets your typesize. (e.g. p2
is a pointer-to-pointer-to char
), so when you dereference it (i.e. remove one-level of indirection), you are left with a pointer-to char
). Which is what you are needing to allocate (pointers). So sizeof *p2 * 2
allocates a block of memory large enough to store two pointers to char
. There is no guessing about how many '*'
s you add to char
, you just use the dereferenced pointer -- and the compiler automatically knows how much storage the type requires. Then p2 = malloc ....
assigns the starting address for that block of memory to p2
.
(note: you must validate that the call to malloc
succeeded by checking the return, e.g. if (p2 == NULL)
the allocation failed.)
That is basically it. The typesize controls pointer arithmetic. So if you have a pointer to char
, (say char *p;
), p++;
advances the pointer by 1-byte, to the next character. If you have a pointer to int
, (say int *pi;
), then pi++;
advances the pointer by 4-bytes so it is pointing to the next integer.
With that as the background, what it looks like you are attempting to do is something like the following short example. (note: I have changed the output format to make it read easier)
#include <stdio.h>
#include <stdlib.h>
int main (void) {
/* don't cast the return of malloc,
* use dereferenced pointer as typesize.
* allocates 2 pointers, p2[0], p2[1] - unused.
*/
char **p2 = malloc (sizeof *p2 * 2);
if (!p2) { /* validate every allocation */
perror ("malloc-p2");
return 1;
}
/* use p2[0] instead of *(p2 + 0), i.e. *p2
* allocates 3-chars of storage, assigns starting address to p2[0].
*/
p2[0] = malloc (sizeof *p2[0] * 3);
if (!p2[0]) { /* ditto */
perror ("malloc-p2[0]");
return 1;
}
**p2 = 's'; /* assigns 's' to first char of first pointer */
/* you can't advance p2 or you lose your original pointer (memory-leak) */
char *ptmp = p2[0]; /* use a temp pointer instead to 1st 3-chars */
ptmp++; /* advance the temp pointer */
*ptmp = 'k'; /* assign 'k' as 2nd char */
printf (" p2 (%p)\n %c (%p)\n %c (%p)\n",
(void*)p2, p2[0][0], (void*)&p2[0][0], p2[0][1], (void*)&p2[0][1]);
free (p2[0]); /* free storage */
free (p2); /* free pointers */
}
(note: the address of p2
cannot be changed. If you assign something else to p2
or advance p2++;
it no longer points to the start of your allocated memory block, so (unless you save a copy of the pointer) the block of memory can no longer be freed resulting in a memory-leak.)
Example Use/Output
$ ./bin/ptr2ptrfun
p2 (0x73a010)
s (0x73a030)
k (0x73a031)
You see above p2
is the pointer to the block of memory for 2-pointers, 0x73a010
, while the address where 's'
and 'k'
are stored are sequential and within the storage for 3-characters created by the second call to malloc
with the starting address assigned to the first pointer allocated (the second pointer you allocate remains unused)
Memory Use/Error Check
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind
is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/ptr2ptrfun
==8767== Memcheck, a memory error detector
==8767== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8767== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==8767== Command: ./bin/ptr2ptrfun
==8767==
p2 (0x51db040)
s (0x51db090)
k (0x51db091)
==8767==
==8767== HEAP SUMMARY:
==8767== in use at exit: 0 bytes in 0 blocks
==8767== total heap usage: 2 allocs, 2 frees, 19 bytes allocated
==8767==
==8767== All heap blocks were freed -- no leaks are possible
==8767==
==8767== For counts of detected and suppressed errors, rerun with: -v
==8767== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
That's pointers in a nutshell. Let me know if you have any further questions.