Search code examples
cpointersmultidimensional-arraydynamic-memory-allocation

Double pointer vs array of pointers(**array vs *array[])


Im not clearly sure what is the difference between those 2. My professor wrote that **array is same as *array[] and we were presented an example where he used **array (so after classes I tried exchanging that with *array[] and it didn't work), could anyone tell me if those 2 are actually the same as he wrote?? Anyway the class was about dynamic memory allocation

@As soon as I changed the double pointer, this line started throwing error

    lines = malloc(sizeof(char*));

and a few others where the memory is beeing reallocated

@2 Hell yeah, here's the whole code

And for those comments bellow, nope there is nothing inside [] because his statement was

    **array = *array[]

BIG UPDATE

I am so sorry for any inconvenience, I was just too tired at the time of writing this post, here is the whole code with no edits

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

    char **lines;     // global text buffer, organized as an array of lines

    // --------------------------------------------------------------------------------
    // initialize global buffer
    void initialize()
    {
      lines = malloc(sizeof(char*));
      lines[0] = NULL;
    }

    // --------------------------------------------------------------------------------
    // return number of lines in buffer
    int countLines()
    {
      int count = 0;
      while(lines[count++]) ;
      return count-1;
    }

    // --------------------------------------------------------------------------------
    // print one line
    void printLine(int line)
    {
      printf("Line %d: %p %p %s\n",line, &lines[line], lines[line], lines[line]);
    }

    // --------------------------------------------------------------------------------
    // print all lines
    void printAll()
    {
      int num_lines = countLines();
      int line = 0;
      printf("----- %d line(s) ----\n",num_lines);
      while (line < num_lines)
        printLine(line++);
      printf("---------------------\n");
    }

    // --------------------------------------------------------------------------------
    // free whole buffer
    void freeAll()
    {
      int line = countLines();
      while (line >= 0)
        free(lines[line--]);
      free(lines);
    }

    // --------------------------------------------------------------------------------
    // insert a line before the line specified
    void insertLine(int line, char *str)
    {
      int num_lines = countLines();

      // increase lines size by one line pointer:
        lines = realloc(lines, (num_lines+2) * sizeof(char*));

      // move line pointers backwards:
      memmove(&lines[line+1], &lines[line], (num_lines-line+1)*sizeof(char*));

      // insert the new line:
      lines[line] = malloc(strlen(str)+1);
      strcpy(lines[line],str);
    }

    // --------------------------------------------------------------------------------
    // remove the specified line
    void removeLine(int line)
    {
      int num_lines = countLines();

      // free the memory used by this line:
      free(lines[line]);

      // move line pointers forward:
      memmove(&lines[line], &lines[line+1], (num_lines-line+1)*sizeof(char*));

      // decrease lines size by one line pointer:
        lines = realloc(lines, num_lines * sizeof(char*));
    }

    // --------------------------------------------------------------------------------
    // insert a string into specified line at specified column
    void insertString(int line, int col, char *str)
    {
      // make room for the new string:
      lines[line] = realloc(lines[line], strlen(lines[line])+strlen(str)+1);

      // move characters after col to the end:
      memmove(lines[line]+col+strlen(str), lines[line]+col, strlen(lines[line])-col);

      // insert string (without terminating 0-byte):
      memmove(lines[line]+col, str, strlen(str));
    }

    // --------------------------------------------------------------------------------
    // MAIN program
    int main()
    {
      initialize();

      printAll();
      insertLine(0,"Das ist");
      printAll();
      insertLine(1,"Text");
      printAll();
      insertLine(1,"ein");
      printAll();
      insertLine(2,"kurzer");
      printAll();
      printf("lines[2][4] = %c\n",lines[2][4]);
      insertString(2,0,"ziemlich ");
      printAll();
      removeLine(2);
      printAll();

      freeAll();
      return 0;
    }

Solution

  • If the code you reference in your question was given to you by your professor as an example of the use of pointer arrays of pointers to pointers, I'm not sure how much good that class will actually do. I suspect it was either provided as a debugging exercise or it may have been your attempt at a solution. Regardless, if you simply compile with Warnings enabled, you will find a number of problems that need attention before you advance to debugging your code.

    Regarding the code you reference, while you are free to use a global text buffer, you are far better served by not using a global buffer and passing a pointer to your data as required. There are some instances, various callback functions, etc. that require global data, but as a rule of thumb, those are the exception and not the rule.

    Your question basically boils down to "How do I properly use an array of pointers and double-pointers (pointer-to-pointer-to-type) variables. There is no way the topic can be completely covered in one answer because there are far too many situations and contexts where one or the other can be (or should be) used and why. However, a few examples will hopefully help you understand the basic differences.

    Starting with the array of pointers to type (e.g. char *array[]). It is generally seen in that form as a function argument. When declared as a variable it is followed with an initialization. e.g.:

    char *array[] = { "The quick",
                      "brown fox",
                      "jumps over",
                      "the lazy dog." };
    

    char *array[]; by itself as a variable declaration is invalid due to the missing array size between [..]. When used globally, as in your example, the compiler will accept the declaration, but will warn the declaration is assumed to have one element.

    The elements of array declared above are pointers to type char. Specifically, the elements are pointers to the string-literals created by the declaration. Each of the strings can be accessed by the associated pointer in array as array[0], ... array[3].

    A pointer to pointer to type (double-pointer), is exactly what its name implies. It is a pointer, that holds a pointer as its value. In basic terms, it is a pointer that points to another pointer. It can be used to access the members of the array above by assigning the address of array like:

    char **p = array;
    

    Where p[1] or *(p + 1) points to "brown fox", etc.

    Alternatively, a number of pointer to pointer to type can be dynamically allocated and used to create an array of pointers to type, that can then be allocated and reallocated to handle access or storage of an unknown number of elements. For example, a brief example to read an unknown number of lines from stdin, you might see:

    #define MAXL 128
    #define MAXC 512
    ...
    char **lines = NULL;
    char buf[MAXC] = {0};
    lines = malloc (MAXL * sizeof *lines);
    size_t index = 0;
    ...
    while (fgets (buf, MAXC, stdin)) {
        lines[index++] = strdup (buf);
        if (index == MAXL)
            /* reallocate lines */
    }
    

    Above you have lines, a pointer-to-pointer-to-char, initially NULL, that is use to allocate MAXL (128) pointers-to-char. Lines are then read from stdin into buf, after each successful read, memory is allocated to hold the contents of buf and the resulting start address for each block of memory is assigned to each pointer line[index] where index is 0-127, and upon increment of index to 128, index is reallocated to provide additional pointers and the read continues.

    What makes the topic larger than can be handled in any one answer is that an array of pointers or pointer to pointer to type can be to any type. (int, struct, or as a member of a struct to different type, or function, etc...) They can be used linked-lists, in the return of directory listings (e.g opendir), or in any additional number of ways. They can be statically initialized, dynamically allocated, passed as function parameters, etc... There are just far too many different contexts to cover them all. But in all instances, they will follow the general rules seen here and in the other answer here and in 1,000's more answers here on StackOverflow.

    I'll end with a short example you can use to look at the different basic uses of the array and double-pointer. I have provided additional comments in the source. This just provides a handful of different basic uses and of static declaration and dynamic allocation:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main (void) {
    
        /* array is a static array of 4 pointers to char, initialized to the 
           4 string-literals that a part of the declaration */
        char *array[] = { "The quick",
                        "brown fox",
                        "jumps over",
                        "the lazy dog." };
        /* p is a pointer-to-pointer-to-char assigned the address of array */
        char **p = array;
        /* lines is a pointer-to-pointer-to-char initialized to NULL, used
           below to allocate 8 pointers and storage to hold 2 copes of array */
        char **lines = NULL;
        size_t narray = sizeof array/sizeof *array;
        size_t i;
    
        printf ("\nprinting each string-literal at the address stored by\n"
                "each pointer in the array of ponters named 'array':\n\n");
        for (i = 0; i < narray; i++)
            printf (" %s\n", array[i]);
    
        printf ("\nprinting each string using a pointer to pointer to char 'p':\n\n");
        for (i = 0; i < narray; i++, p++)
            printf (" %s\n", *p);
    
        p = array;
        printf ("\nprinting each line using a pointer to pointer"
                " to char 'p' with array notation:\n\n");
        for (i = 0; i < narray; i++)
            printf (" %s\n", p[i]);
    
        /* allocate 8 pointers to char */
        lines = malloc (2 * narray * sizeof *lines);
    
        /* allocate memory and copy 1st 4-strings to lines (long way) */
        for (i = 0; i < narray; i++) {
            size_t len = strlen (array[i]);
            lines[i] = malloc (len * sizeof **lines + 1);
            strncpy (lines[i], array[i], len);
            lines[i][len] = 0;
        }
    
        /* allocate memory and copy 1st 4-strings to lines 
           (using strdup - short way) */
        // for (i = 0; i < narray; i++)
        //     lines[i] = strdup (array[i]);
    
        /* allocate memory and copy again as last 4-strings in lines */
        p = array;
        for (i = 0; i < narray; i++, p++)
            lines[i+4] = strdup (*p);
    
        p = lines; /* p now points to lines instead of array */
        printf ("\nprinting each allocated line in 'lines' using pointer 'p':\n\n");
        for (i = 0; i < 2 * narray; i++)
            printf (" %s\n", p[i]);
    
        /* free allocated memory */
        for (i = 0; i < 2 * narray; i++)
            free (lines[i]);
        free (lines);
    
        return 0;
    }
    

    Let me know if you have any questions. It a large topic with a relatively small set of rules that can be applied in whole lot of different ways and in different contexts.