0

The below code is pointer to "array of pointers" code. I am not sure that this is correct.

  tags[0][0] = malloc(sizeof(char)*5);
  strcpy(tags[0][0],"hi");
  tags[0][1] = malloc(sizeof(char)*5);
  strcpy(tags[0][1],"h2");

Can anyone please tell are the above lines correct in following code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *(*tags)[2] = malloc (2 * sizeof *tags);
  
    tags[0][0] = malloc(sizeof(char)*5);
    strcpy(tags[0][0],"hi");
    tags[0][1] = malloc(sizeof(char)*5);
    strcpy(tags[0][1],"h2");
    tags[1][0] = "<head";
    tags[1][1] = "</head>";
  
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++)
            printf (" %s", tags[i][j]);
    
        putchar ('\n');
    }
  
    free (tags);
}

Is there any difference between the above and array of pointers to pointers?

Chris
  • 26,361
  • 5
  • 21
  • 42
user786
  • 3,902
  • 4
  • 40
  • 72
  • `sizeof(char)` is `1`. – Carl Norum Nov 09 '21 at 05:54
  • @CarlNorum thanks for comment. I do know this. Is just to specify datatype for readability – user786 Nov 09 '21 at 06:07
  • Ymmv but it’s not very idiomatic. – Carl Norum Nov 09 '21 at 06:45
  • "Is there any difference between the above and array of pointers to pointers?" Yes, it is using adjacent allocation on the heap so it is likely to be much faster. See [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) – Lundin Nov 09 '21 at 09:26

3 Answers3

2

This looks ok to me. I haven't seen pointers like this very much in practice, but I would describe tags as "A pointer to a 2-size array of character pointers". When you malloc (2 * sizeof *tags);, you create space for two of of these 2-size array of character pointers. This is what you have from each line:

char *(*tags)[2] = malloc (2 * sizeof *tags);

tags -----> [char*][char*]   // index 0 of tags
            [char*][char*]   // index 1 of tags

// You now have 4 character pointers in contiguous memory that don't
// point to anything. tags points to index 0, tags+1 points to index 1.
// Each +1 on *(tags) advances the pointer 16 bytes on my machine (the size
// of two 8-byte pointers).

Next malloc:

tags[0][0] = malloc(sizeof(char)*5);

               +-------> 5 bytes
               |
tags ------> [char*][char*]  // index 0
               ^
               index 0 of tags[0], this now points to 5 bytes

After first strcpy

strcpy(tags[0][0],"hi");

               +-------> {'h', 'i', '\0', <2 more> }
               |
tags ------> [char*][char*]  // index 0

Next malloc

tags[0][1] = malloc(sizeof(char)*5);

                      +-------> 5 bytes
                      |
tags ------> [char*][char*]  // index 0
                      ^
                      index 1 of tags[0], this now points to 5 bytes

Next strcpy

strcpy(tags[0][1],"h2");

                      +-------> {'h', '2', '\0', <2 more> }
                      |
tags ------> [char*][char*]  // index 0

And finally, the string literal assignments

tags[1][0] = "<head";
tags[1][1] = "</head>";

tags -----> [char*][char*]   // index 0 of tags
            [char*][char*]   // index 1 of tags
              |       |
              |       |------> points to string literal "</head>"
              |--------------> points to string literal "<head"

If you really want to clean up properly, you should

free(tags[0][1]);
free(tags[0][0]);
// the order of the above doesn't really matter, I just get in the
// habit of cleaning up in the reverse order that I malloced in.
// But if you free(tags) first, you've created a memory leak, as
// there's now no existing pointers to tags[0][0] or [0][1].
free(tags);

Of course, all memory gets reclaimed by the OS as soon as the process exits anyway.

yano
  • 4,827
  • 2
  • 23
  • 35
  • isn't `char *(*tags)[2]` is "pointer to array of pointers" if u read with C spiral-Clockwise rule – user786 Nov 09 '21 at 09:29
  • @user786 In practice you can just think of it as a 2D array of pointer items. Such a type would most correctly have been declared as `item (*arr)[x][y]` but that makes de-referencing problematic, since all pointer to array types come with an extra level of indirection. We'd have to type `(*arr)[i][j]` which is cumbersome. A common trick to avoid such is to ditch one dimension in the declaration: `item (*arr)[y] = malloc( sizeof(item[x][y]);` and then we can use it as `arr[i][j]`. – Lundin Nov 09 '21 at 09:32
  • @Lundin but its three d array isn't it? – user786 Nov 09 '21 at 09:39
  • 1
    @user786 No it is a 2D array where each item is a pointer. It can be used to emulate a 3D-like syntax because you've set each pointer item to point at an array, but it is not a 3D array. – Lundin Nov 09 '21 at 09:42
2

Yes, the code principle is absolutely correct. It's the correct way to do dynamic allocation of a 2D array.

This

char *(*tags)[2] 

means that tags is a pointer to an array of 2 char pointers.

So when you do

char *(*tags)[2] = malloc (2 * sizeof *tags);

you allocate an array of two arrays of two char pointers. It's equivalent to:

char* tags[2][2];

when not using dynamic allocation.

So yes, the code is fine. That's exactly the way to do it. And you should always do like that when you want a dynamic 2D array.

However, your code lacks:

free(tags[0][0]);
free(tags[0][1]);

BTW: Storing both pointers to dynamic allocated memory and pointers to string literals in the same array is a bit "strange" and can be problematic as you need to track which pointers to free and which not to free.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
0

What you want to achieve is array of strings.

char ***tags = malloc(sizeof(char**) * numberofpointers);

for (int i = 0; i < numberofpointers; ++i)
{
    tags[i] = malloc(sizeof(char*) * numberofstringsperpointer);
}

for (int i = 0; i < numberofpointers; ++i)
{
    for (int j = 0; j < numberofstringsperpointer; ++j)
    {
        tags[i][j] = malloc(sizeof(char) * (numberofcharacterperstring + 1));
    }
}

strcpy(tags[0][0], string)