0

I am trying to use double pointer memory allocation without using index. I am only allowed to use pointer arithmetic to access to address.

char** p2 = (char**) malloc(sizeof(char) * 2);
*p2 = (char*)malloc(sizeof(char) * 3);


**p2 = 's';
printf("%c\n", **p2);
printf("%p\n", **p2);
printf("%c\n", p2[0][0]);
printf("%p\n\n", p2[0][0]);

(**p2)++;
**p2 = 'k';
printf("%c\n", **p2);
printf("%p\n", **p2);
printf("%c\n", p2[0][1]);
printf("%p\n\n", p2[0][1]);

After fail above code,

int* ptr = *p2;
*ptr = 'h';
printf("%c\n", *ptr);
printf("%i\n", *ptr);
printf("%c\n", p2[0][0]);
printf("%i\n\n", p2[0][0]);

(*ptr)++;
*ptr = 'a';
printf("%c\n", *ptr);
printf("%i\n", *ptr);
printf("%c\n", p2[0][1]);
printf("%i\n\n", p2[0][1]);

this one also did not work.

So, I was able to access til p2[0][0], returning s and 0x73.

However, when I tried to access to next index using pointer arithmetic (**p++), **p2 value and p2[0][1] are different.

What pointer arithmetic should I use to access p2[0][1] and more?

Park Bo
  • 53
  • 7
  • 7
    Your frst problem is you allocate 2 chars not 2 char*s so you have undefined behavior at line 2. – John3136 Jun 14 '19 at 02:20
  • 1
    You also don't want to deference your pointer before you increment; `(**ptr)++`. You are trying to access the next place in memory, so you want to increment the address: `ptr++`. But your other one is ok: `(*ptr)++` – Zeke Willams Jun 14 '19 at 02:25
  • `char **p2 = malloc (sizeof *p2 * 2);` then `*p2 = malloc (sizeof **p2 * 3);` (or better `p2[0] = malloc (sizeof **p2 * 3);`) There is no need to cast the return of `malloc`, it is unnecessary. See: [Do I cast the result of malloc?](http://stackoverflow.com/q/605845/995714) – David C. Rankin Jun 14 '19 at 02:35
  • @John3136 I changed to char** p2 = (char**) malloc(sizeof(char* ) * 2); to allocate 2char*s. However, still no able to access to p2[0][1] – Park Bo Jun 14 '19 at 02:36
  • 1
    `(**p2)++;` makes `'s'`, `'t'`, it doesn't advance the pointer, then you overwrite `'t'` with `'k'` doing `**p2 = 'k';`. – David C. Rankin Jun 14 '19 at 02:40
  • This code is loaded with guesses, most of them bad. Do you know what `**p2` is *doing*? And if not, perhaps that's part of the problem. But if you *do* understand, you'll also understand why `printf("%p\n", **p2);` make no sense. `**p2` evaluates to a `char`. A `char` is not a pointer, and thus `%p` as the format specifier makes no sense. – WhozCraig Jun 14 '19 at 03:08
  • This should immediately raise a red flag: `char** p2 = (char**) malloc(sizeof(char) * 2);` So `p2` is a pointer to a pointer to a `char`, so you need to allocate `sizeof(char *)` times however many instances you want (presumably 2). But you're using `sizeof(char)` instead, which is probably only 1/4 or 1/8 the size of what you need. – Tom Karzes Jun 14 '19 at 04:04
  • Related: [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) – Lundin Jun 14 '19 at 07:39

1 Answers1

0

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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85