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.

Answer from David C. Rankin on Stack Overflow
🌐
Medium
medium.com › @muirujackson › use-of-double-pointers-in-c-d94e086a13fc
Use of Double Pointers in C?. In C, we use pointers to hold memory… | by Muiru Jackson | Medium
April 28, 2023 - In the code above, we first declare a double pointer variable “matrix” that will hold the address of the two-dimensional array. We then allocate memory for the rows of the array using the malloc function. Since each row of the array is itself a one-dimensional array, we use a single pointer to hold the address of each row.
🌐
GeeksforGeeks
geeksforgeeks.org › c language › c-pointer-to-pointer-double-pointer
C - Pointer to Pointer (Double Pointer) - GeeksforGeeks
An array of strings is typically ... passed to functions using a double pointer (char **), allowing the function to access and manipulate multiple strings efficiently....
Published: July 18, 2026
Discussions

I don't understand double pointers in C
Here's the critical piece of information you need to reason about pointers. A pointer is a variable that holds an address. That's it. That's all you need to know. From there you should be able to reason about anything pointer related. It will still be mind bendy for a time, but always start there. int i = 42; This is a variable that holds an integer. No problem. int *p = &i; This is a variable that holds an address. Specifically an address of an integer, but don't really worry about that. Just remember, it's an address. int **double_p = &p; This is also a variable that holds an address. The only difference is what it holds an address of. It holds the address of the variable p. p holds an address of an int. student** courses = calloc(*C, sizeof(student*)); To help understand this, take a step back for a moment. int *foo = calloc(16, sizeof(int)); foo holds the address of an int. calloc sets aside enough memory for 16 ints, and then returns the address of the "zeroth" element of that dynamically allocated array. foo then holds the address of the start of that array. OK, now back to the confusing line of code. student** courses = calloc(*C, sizeof(student*)); courses holds an address. The type of data at that address? Another pointer, specifically, a pointer to a student struct. So, calloc will set aside enough memory for an array to hold a bunch of memory addresses. calloc is going to return the address of the "zeroth" element of that array. Each element of that array will be able to hold a memory address of a student struct. More on reddit.com
🌐 r/learnprogramming
4
2
September 9, 2020
Double pointer array in c++ - Stack Overflow
I was reading a program about BTree, there I came across this : BTreeNode **C. I understand that it is a 2d array but it was initialized as C=new BTreeNode *[2*t];. I can't understand this: is this... More on stackoverflow.com
🌐 stackoverflow.com
Double Pointer to Array in C - Stack Overflow
When you indirect through the pointer ... + i is a pointer to an integer (i'th sibling of the first element of the first array). What would happen if instead I use double pointers (q in the above code).... More on stackoverflow.com
🌐 stackoverflow.com
June 4, 2021
How do I use a double pointer to manipulate an array in C? - Stack Overflow
You're dereferencing an uninitialized pointer that doesn't point anywhere, leading to undefined behavior. Why do you need a "double pointer"? Arrays naturally decays to pointers to their first element, so using plain arr is equal to &arr[0] and it has the type int *. More on stackoverflow.com
🌐 stackoverflow.com
Top answer
1 of 3
24

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.

2 of 3
15

My professor wrote that **array is same as *array[]

That is true in some contexts and not true in other contexts.

If used in a function as argument,

void foo(int **array) {}

is the same as

void foo(int *array[]) {}

When declared as variables,

int **array;

is not the same as

int *array[];

Re comment "Why is int **array; not the same as int *array[];?"

Let's address the question with int *array and int array[].

Here are some of the valid and invalid ways to declare the variables.

int *array; // Ok. Uninitialized pointer.
int *array = nullptr; // ok.
int *array = new int; // ok.
int *array = new int[10]; // ok.

const size = sizeof(array); // Size of a pointer. For 32 bit
                            // pointers, this will be 4.
                            // For 64 bit pointers, this will be 8

Contrast that with

int array[]; // Not ok.
int array[] = nullptr; // Not ok.
int array[] = new int; // Not ok.
int array[] = new int[10]; // Not ok.
int array[] = {10, 20, 30}; // Ok.
int array[3] = {10, 20, 30}; // Ok. same as previous

const size1 = sizeof(array); // (size of int)*3
                             /// Unrelated to size of pointers

int array[5] = {10, 20, 30}; // Ok.
int array[5] = {10, 20, 30, 0, 0}; // Same as previus.

const size2 = sizeof(array); // (size of int)*5
                             /// Unrelated to size of pointers

More on the differences:

  1. int *array; is sufficent to declare a variable while int array[] is not. Size of the array must be known at compile time for the latter.

  2. sizeof operator works diferently for int *array; and int array[];.

  3. A variable declared with int * can be reassigned to point to a different location while a variable declared with int [] cannot be reassigned.

    int *array = nullptr;
    array = new int; // Syntactically ok.
    array = new int[10]; // Syntactically ok.
    

    OTOH

    int array[] = {10, 20, 30};
    array = new int[3]; // Not ok. Compiler error.
    array = {50, 60, 70}; // Not ok. Compiler error.
    
  4. A variable declared with int * can point to statically allocated memory as well as dynamically allocated memory. A variable declared with int [] references only statically defined and constant size memory.

The differences apply equally to int **array; and int *array[];.

🌐
EDUCBA
educba.com › home › software development › software development tutorials › c programming tutorial › double pointer in c
Double Pointer in C | How does Double Pointer work in C with Examples
April 1, 2023 - Explanation: In the above code, as “matrix” is a double pointer it uses malloc function which dynamically allocates memory for the matrix of 5 rows and 5 columns. As we know that in the code “matrix” is integer data type so integer pointer can be used in the starting of the array as the address of the “matrix” pointer is an integer.
Address: Unit no. 202, Jay Antariksh Bldg, Makwana Road, Marol, Andheri (East),, 400059, Mumbai
🌐
Reddit
reddit.com › r/learnprogramming › i don't understand double pointers in c
r/learnprogramming on Reddit: I don't understand double pointers in C
September 9, 2020 -

So I understand pointers. An int * would point to the address of an integer. I understand how you could have a struct pointer and all that. I even sort of understand double pointers. An int ** would be a pointer, pointing to another pointer, which is pointing to an integer. I think that's right but I could be wrong. It's just when I see it in code my brain has a hard time grasping it. I'm looking at a past lab from a course to try to understand it, and I just don't really get it.

So in the lab we were given a struct student, which in itself has two pointer variables among others. We have to read in a file, with the first line containing three integers. The first is the number of courses, C. The next integer is N, which is the number of students per course. In the code, they do fscanf to take in the first few integers. I understand that. Then they allocate memory for courses using calloc.

The line is: student** courses = calloc(*C, sizeof(student*));

This is all inside a function which returns another student**. This is where I get lost. A struct double pointer still is hard for me to grasp. My friend said it's like an array of structs, but I still don't really get it.

Maybe if someone could explain them, or give me a resource that will explain them I would really appreciate it.

Top answer
1 of 3
2
Here's the critical piece of information you need to reason about pointers. A pointer is a variable that holds an address. That's it. That's all you need to know. From there you should be able to reason about anything pointer related. It will still be mind bendy for a time, but always start there. int i = 42; This is a variable that holds an integer. No problem. int *p = &i; This is a variable that holds an address. Specifically an address of an integer, but don't really worry about that. Just remember, it's an address. int **double_p = &p; This is also a variable that holds an address. The only difference is what it holds an address of. It holds the address of the variable p. p holds an address of an int. student** courses = calloc(*C, sizeof(student*)); To help understand this, take a step back for a moment. int *foo = calloc(16, sizeof(int)); foo holds the address of an int. calloc sets aside enough memory for 16 ints, and then returns the address of the "zeroth" element of that dynamically allocated array. foo then holds the address of the start of that array. OK, now back to the confusing line of code. student** courses = calloc(*C, sizeof(student*)); courses holds an address. The type of data at that address? Another pointer, specifically, a pointer to a student struct. So, calloc will set aside enough memory for an array to hold a bunch of memory addresses. calloc is going to return the address of the "zeroth" element of that array. Each element of that array will be able to hold a memory address of a student struct.
2 of 3
1
A pointer can be thought of as a reference to something else by specifying the location of that thing. If you want to deliver a package to my house, you aren’t going to ask me to bring my house to you because that’s silly. Instead, you ask me for the address of my house and then you go to that address and drop off the package. You have to dereference (go to an address) just once to get to the house, so this is a single pointer. A pointer to a pointer is the same thing, just with another reference layer. I don’t want anyone to overhear where my house is, so instead of telling you it’s address directly, I write the address on a piece of paper and I hide it somewhere. I then tell you where to find the piece of paper. To get to my house, you first have to go to the paper’s address, read it, then go to the address that’s written on it. You have to dereference twice in order to get to the house, so this is a double pointer.
🌐
MYCPLUS
mycplus.com › home › programming
Double Pointer in C (Pointer to Pointer): Guide + Examples
February 10, 2021 - A double pointer stores the address of a pointer: pptr → address of ptr, *pptr → ptr‘s content, **pptr → the value. Every * is one hop. The essential use case: pass T ** when a function must modify the caller’s T * — allocation ...
🌐
TutorialsPoint
tutorialspoint.com › double-pointer-pointer-to-pointer-in-c
Pointer to Pointer (Double Pointer) in C
August 30, 2019 - If you do need to have a pointer to "c" (in the above example), it will be a "pointer to a pointer to a pointer" and may be declared as − ... Mostly, double pointers are used to refer to a two−dimensional array or an array of strings.
Find elsewhere
Top answer
1 of 3
9

You probably well know that double* is a pointer to a double element. In the same way, double** is a pointer to a double* element, which is itself a pointer. Again, double*** is a pointer to a double** element, and so on.

When you instanciate an array to a type T, you usually do new T [size];. For example, for an array of double, you write new double[size];. If your type T is a pointer itself, it's exactly the same : you write new double*[size];, and you get an array of pointers.

In your case, BTreeNode* is a pointer to BTreeNode, and BTreeNode** is a pointer to BTreeNode* which is a pointer to BTreeNode. When you instanciate it by doing new BTreeNode*[size]; you get an array of pointers to BTreeNode elements.

But actually, at this step you don't have a 2D array, because the pointers in your freshly allocated array are NOT allocated. The usual way to do that is the following example :

int num_rows = 10;
int num_cols = 20;
BTreeNode** C = new BTreeNode*[num_rows];
for(int i = 0; i < num_rows; i++)
{
  // Then, the type of C[i] is BTreeNode*
  // It's a pointer to an element of type BTreeNode
  // This pointer not allocated yet, you have now to allocate it
  C[i] = new BTreeNode [num_cols];
}

Don't forget to delete your memory after usage. The usual way to do it is the following :

for(int i = 0; i < num_rows; i++)
  delete [] C[i];
delete [] C;
2 of 3
0

The statement C=new BTreeNode *[2*t]; allocates space for 2*t instances of type BTreeNode * and therefore returns a type BTreeNode ** pointing to the first element of such instances. This is the first dimension of your array, however no memory has been allocated for the second dimension.

🌐
Zakuarbor
zakuarbor.github.io › blog › double-pointers
A look at Double Pointers - RANDOM BITS
October 25, 2020 - Today I want to discuss with you two use cases for double pointers: making modifications to a pointer in a function and in 2d arrays.
Top answer
1 of 2
2

For starters this assignment

q = A;

is incorrect because the left operand (having the type int ( ** )[3]) and the right operand (having the type int ( * )[3] after the implicit conversion of the array designator to a pointer to its first element) have different types and there is no implicit conversion between the types.

The compiler can issue an error like this

error: assignment to ‘int (**)[3]’ from incompatible pointer type ‘int (*)[3]’

You could write instead

q = &p;

In this call of printf

printf("*p is : %d\n", *p);

there is used an incorrect argument. The type of the expression *p is int[3]. So in fact you are trying to output a pointer (due to implicit conversion of the array designator to a pointer to its first element) using the conversion specifier %d that is designed to output integers.

Here is a demonstrative program.

#include <stdio.h>

int main(void) 
{
    int A[2][3] = {{1100, 1200, 1300}, {1400, 1500, 1600}};
    int (*p)[3], (**q)[3];
    p = A;
    q = &p;
    
    printf( "A  is : %p\n", ( void * )A );
    printf( "*p is : %p\n", ( void * )*p );
    printf( "*q is : %p\n", ( void * )*q);
    
    return 0;
}

Its output might look like

A  is : 0x7ffdb1c214e0
*p is : 0x7ffdb1c214e0
*q is : 0x7ffdb1c214e0

That is the first call of printf output the initial address of the first element A[0] (of the type int[3]) of the two-dimensional array.

The second call of printf outputs the address of the first element of the first "row" of the two-dimensional array that is &A[0][0].

The third call of printf outputs the value stored in the pointer p that is the address of the first "row" of the two-dimensional array A.

If you want to output the first elements of the array A using the pointers then the program can look the following way.

#include <stdio.h>

int main(void) 
{
    int A[2][3] = {{1100, 1200, 1300}, {1400, 1500, 1600}};
    int (*p)[3], (**q)[3];
    p = A;
    q = &p;
    
    printf( "**p  is : %d\n", **p );
    printf( "***q is : %d\n", ***q);
    
    return 0;
}

Now the program output is

**p  is : 1100
***q is : 1100
2 of 2
1

p is a pointer to integer array of size 3.

Correct.

*p + i points to ith array in A i.e. A[i].

Incorrect. p + i would be a pointer to an array.

When you indirect through the pointer to array, the result is an array, and when you add an integer to an array, the array decays to pointer to element of that array and since *p is an array of integers, the decayed pointer points to an integer element of the array. Thus, the result of *p + i is a pointer to an integer (i'th sibling of the first element of the first array).

What would happen if instead I use double pointers (q in the above code).

I assume that by substituted above code, you mean *q + i.

If you have a pointer to a pointer to an array, then indirecting through the pointer results in a pointer to an array. Adding an integer to pointer to an array gives you pointer to an array that is a sibling.


q = A;

This assignment is ill-formed in C++. An array of arrays of integers is not convertible to a pointer to pointer to an array.

printf("*p is : %d\n", *p);
printf("*q is : %d", *q);//Why ?  

%d is an invalid format specifier for int* as well as for a int (*)[3]. By using invalid format specifier, the behaviour of this program is undefined. That explains all of the behaviour.

🌐
Codedamn
codedamn.com › news › c programming
What are double pointers in C?
March 10, 2024 - While a single pointer directly points to the data, a double pointer points to a pointer that then points to the data. This difference is critical in scenarios where the ability to modify the address a pointer points to is necessary, such as ...
Top answer
1 of 3
3

Let’s start with:

int arr[][4]={{1,2,3,4},
              {5,6,7,8},
              {9,10,11,12},
             };

print(arr,3,4);

In print(arr,3,4);, arr is an array. Specifically, it is an array of 3 elements, each of which is an array of 4 elements, each of which is an int. Thus, arr is an array of 3 arrays of 4 int. You have probably heard or read that arrays “decay” to pointers. This is a colloquial term. The actual rule, which you can find in clause 6.3.2.1, paragraph 3, of the C 2011 standard, is:

Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue.

Here is how this rule applies to arr in print(arr,3,4);:

  • arr is an identifier, meaning it is the name of some object. As such, it designates its object. That object is an array of 3 arrays of 4 int.
  • This array is not the operand of sizeof or Alignof or &, and it is not a string literal. So, following the rule, it is converted from an array of 3 arrays of 4 int to a pointer to the first array of 4 int.

What happens next? Nothing. The expression we have is a pointer to an array of 4 int. There is no rule that says a pointer to an array is converted to a pointer to a pointer. We have a pointer to an array, but that array is not used in an expression yet, not in the simple expression arr. So it is not converted.

This means what you are passing to print is a pointer to an array of 4 int. But your declaration for print says it takes a pointer to a pointer to an int. Those are different things, and they are incompatible, so the compiler warns you.

(To see they are incompatible, consider the difference to a pointer to an array of 4 int and a pointer to a pointer to int. The memory at a pointer to an array of 4 int contains 4 int values. The memory at a pointer to a pointer to an int contains a pointer. These are very different things.)

Next, consider:

void print (int **A, int m, int n)
…
    printf("%d  ", *((A+(m * 4) + n)));

We know from above that you ought to change int **A to int (*A)[4], which is a pointer to an array of 4 int. You can also change it to int A[][4], because there is a rule in C that such a parameter declaration will be automatically adjusted to be int (*A)[4], as a convenience. However, suppose you keep it as int **A. Then does *((A+(m * 4) + n)) mean?

Since A is a pointer to a pointer to an int, then A+(m * 4) means to add m * 4 to the pointer. (That is strange spacing, by the way. m and 4 are more tightly bound by the higher-precedence multiplication than A and (m * 4) are by the addition, so why do they have looser spacing? A + m*4 would portray the meaning better.) Then A+(m * 4) + n means to add n to that. In total, we have moved m*4+n elements beyond where A points. Since A points to a pointer, we have advanced the pointer by m*4+n pointers. Then *((A+(m * 4) + n))) dereferences that. When you dereference a pointer to a pointer to an int, you get a pointer to an int. So the result of this expression is a pointer. But you wanted an int.

The link you reference talks about a “2D array”. The kinds of arrays it talks about are implemented using pointers to pointers. To create such an array, you create an array of pointers, and then you set each of those pointers to point to the elements of a row. Then a pointer to that array of pointers acts like a 2D array, in that A[i][j] refers to element j of row i. If you had an array like that, you could refer to element n of row m using A[m][n]. Equivalently, you could refer to it with *(*(A+m)+n). What this expression means is:

  • Take the pointer A and add m to it. Since A points to a pointer to an int, adding m advances the value of the pointer to point to m pointers further along. That is where we should find the pointer to the elements of row m.
  • *(A+m) gets the value of the pointer that A+m points to. This value should be a pointer to the elements of row m, specifically a pointer to the first element (with index 0).
  • *(A+m)+n advances the value of the pointer to point n int further along. That is where we should find element n of row m.
  • *(*(A+m)+n) gets the value of the int that *(A+m)+n points to.

Now suppose instead you changed print to be print(int A[][4], int m, int n). Then your printf statement should use A[m][n], just as before. Or it could use *(*(A+m)+n), also just as before. But, in this case, the expression is evaluated:

  • A is a pointer to an array of 4 int. Adding m to it advances value of the pointer to point m arrays further along.
  • *(A+m) gets the object that A+m points to. This object is an entire array. So this is an expression that designates an array. Following the C rule about arrays in expressions, this array is converted to a pointer to its first element. Thus, *(A+m) becomes a pointer to the first element of the array numbered m.
  • *(A+m)+n advances the value of the pointer to point n int further along. That is where we should find element n of row m.
  • *(*(A+m)+n) gets the value of the int that *(A+m)+n points to.

Thus A[m][n] has the same end result for pointers-to-pointers, for pointers-to-arrays, and for arrays-of-arrays, but the steps it goes through for each are different. C knows the types of each subexpression and processes them differently, to achieve the same result.

Finally, suppose you pass &A[0][0] to print and change its parameter to int *A. Now what is the expression *((A+(m * 4) + n)))? In this case, you are treating the array of 3 arrays of 4 int as one big array of 12 int. Then you calculate where element n of row m is. In this case, A is a pointer to int (not a pointer to a pointer to int). So A+(m * 4) + n is a calculation to where element n of row m ought to be, and *((A+(m * 4) + n))) gets the value of that element.

That is a method you ought to avoid when possible. Generally, you should use C’s built-in methods of addressing array elements and avoid doing your own calculations. Whether it is strictly conforming C code or not may depend on how pedantic you are about interpreting certain passages in the C standard.

2 of 3
2

You expectation that a 2D array will decay to a pointer to a pointer is ill-founded.

To be able to use arr as an argument to print, you have the following options.

  1. Change print to

    void print (int (*A)[4], int m){ // Not need for n. It is 4
    
  2. Change print to use a VLA. For this to work, m and n have to come before A.

    void print(int m, int n, int A[m][n] {
    

Both these changes will require you to change the call also.

🌐
BeginnersBook
beginnersbook.com › 2014 › 01 › c-pointer-to-pointer
C – Pointer to Pointer (Double Pointer) with example
Value of num is: 123 Value of num using pr2 is: 123 Value of num using pr1 is: 123 Address of num is: XX771230 Address of num using pr2 is: XX771230 Address of num using pr1 is: XX771230 Value of Pointer pr2 is: XX771230 Value of Pointer pr2 using pr1 is: XX771230 Address of Pointer pr2 is: 66X123X1 Address of Pointer pr2 using pr1 is: 66X123X1 Value of Pointer pr1 is: 66X123X1 Address of Pointer pr1 is: XX661111 · There are some confusions regarding the output of this program, when you run this program you would see the address similar to this: 0x7fff54da7c58.
🌐
DEV Community
dev.to › noah11012 › double-pointers-in-cc-2n96
Double Pointer C: Double Pointers in C/C++ - DEV Community
January 12, 2019 - As you become more comfortable with the idea of double pointers and use them when necessary in your own code, you start to think how silly that once you were afraid of double pointers. And now I send you off with your new knowlegde! ... In this case, it's used as an array of char *, your program arguments.
🌐
Brynmawr
cs.brynmawr.edu › Courses › cs246 › spring2014 › Slides › 16_2DArray_Pointers.pdf pdf
4/1/14 1 2D Arrays and Double Pointers Bryn Mawr College
• A possible way to make a double pointer work with a · 2D array notation: o use an auxiliary array of pointers, o each of them points to a row of the original matrix. int A[m][n], *ptr1, **ptr2; ptr2 = &ptr1; ptr1 = (int *)A; WRONG · int A[m][n], *aux[m], **ptr2; ptr2 = (int **)aux; for ...
Top answer
1 of 4
47

Is 2d array a double pointer?

No. This line of your program is incorrect:

int **ptr = (int**)matrix;

This answer deals with the same topic

If you want concrete image how multidimensional arrays are implemented:

The rules for multidimensional arrays are not different from those for ordinary arrays, just substitute the "inner" array type as element type. The array items are stored in memory directly after each other:

matrix: 11 22 33 99 44 55 66 110
        -----------               the first element of matrix
                    ------------  the second element of matrix

Therefore, to address element matrix[x][y], you take the base address of matrix + x*4 + y (4 is the inner array size).

When arrays are passed to functions, they decay to pointers to their first element. As you noticed, this would be int (*)[4]. The 4 in the type would then tell the compiler the size of the inner type, which is why it works. When doing pointer arithmetic on a similar pointer, the compiler adds multiples of the element size, so for matrix_ptr[x][y], you get matrix_ptr + x*4 + y, which is exactly the same as above.

The cast ptr=(int**)matrix is therefore incorrect. For once, *ptr would mean a pointer value stored at address of matrix, but there isn't any. Secondly, There isn't a pointer to matrix[1] anywhere in the memory of the program.

Note: the calculations in this post assume sizeof(int)==1, to avoid unnecessary complexity.

2 of 4
13

No. A multidimensional array is a single block of memory. The size of the block is the product of the dimensions multiplied by the size of the type of the elements, and indexing in each pair of brackets offsets into the array by the product of the dimensions for the remaining dimensions. So..

int arr[5][3][2];

is an array that holds 30 ints. arr[0][0][0] gives the first, arr[1][0][0] gives the seventh (offsets by 3 * 2). arr[0][1][0] gives the third (offsets by 2).

The pointers the array decays to will depend on the level; arr decays to a pointer to a 3x2 int array, arr[0] decays to a pointer to a 2 element int array, and arr[0][0] decays to a pointer to int.

However, you can also have an array of pointers, and treat it as a multidimensional array -- but it requires some extra setup, because you have to set each pointer to its array. Additionally, you lose the information about the sizes of the arrays within the array (sizeof would give the size of the pointer). On the other hand, you gain the ability to have differently sized sub-arrays and to change where the pointers point, which is useful if they need to be resized or rearranged. An array of pointers like this can be indexed like a multidimensional array, even though it's allocated and arranged differently and sizeof won't always behave the same way with it. A statically allocated example of this setup would be:

int *arr[3];
int aa[2] = { 10, 11 }, 
    ab[2] = { 12, 13 }, 
    ac[2] = { 14, 15 };
arr[0] = aa;
arr[1] = ab;
arr[2] = ac;

After the above, arr[1][0] is 12. But instead of giving the int found at 1 * 2 * sizeof(int) bytes past the start address of the array arr, it gives the int found at 0 * sizeof(int) bytes past the address pointed to by arr[1]. Also, sizeof(arr[0]) is equivalent to sizeof(int *) instead of sizeof(int) * 2.

Top answer
1 of 3
12

When used as a function parameter

char a[]  // compiler interpret it as pointer to char

is equivalent to

char *a

and similarly, in main's signature, char *argv[] is equivalent to char **argv. Note that in both of the cases char *argv[] and char **argv, argv is of type char ** (not an array of pointers!).

The same is not true for the declaration

char **r;
char *a[10];

In this case, r is of type pointer to pointer to char while a is of type array of pointers to char.
The assignment

r = a;   // equivalent to r = &a[0] => r = &*(a + 0) => r = a

is valid because in this expression again array type a will be converted to pointer to its first element and hence of the type char **.

Always remember that arrays and pointers are two different types. The pointers and arrays equivalence means pointer arithmetic and array indexing are equivalent.

Suggested reading:

  • But I heard that char a[] was identical to char *a.
  • Why are array and pointer declarations interchangeable as function formal parameters?
2 of 3
2

argv is an argument so the array is decayed to pointer and there is no way other than size (int c) to differentiate.

When a double pointer and array of pointer are not the arguments, their syntax may look similar sometimes but their type is different and thus the compiler generates different types of code for both.

When the variable of interest is not the function argument, sizeof will give different size for pointer to pointer and array of pointers.

Slightly related question: extern declaration, T* v/s T[]

🌐
SkillVertex
skillvertex.com › blog › c-pointer-to-pointer-double-pointer
C – Pointer To Pointer (Double Pointer)
May 10, 2024 - A pointer-to-pointer (double pointer) is used to store the address of another pointer. The first pointer stores the address of a variable, and the second pointer stores the address of the first pointer.
🌐
Medium
medium.com › @ryan_forrester_ › double-pointers-in-c-a-practical-guide-ffb91c2628b7
Double Pointers in C++: A Practical Guide | by ryan | Medium
January 7, 2025 - This approach allows you to create arrays of varying row lengths, which isn’t possible with a single-dimension dynamic array. Double pointers are useful when you need to modify a pointer passed to a function.