Tags C & C++

Pointers are a great source of confusion in C - newbies have a hard time grasping them. But coupled with arrays, some of the semantics of pointers are complex enough to confound even more seasoned programmers.

Consider this code:

void test(int** p)
{
}


int main()
{
    int arr[] = {30, 450, 14, 5};
    test(&arr);
    return 0;
}

Take a moment to ponder - would you expect this code to compile cleanly?

gcc isn't very happy about it, and issues a warning: passing arg 1 of test from incompatible pointer type. C++ has stricter type checking, so let's try running the same code through g++. As expected, we get an error: cannot convert int (*)[4] to int** for argument 1 to void test(int**)

So what's the problem here? What's wrong with the code above? Well, everything. It's simply invalid, and it makes no sense. Some would think it should work because this works:

void test(int* p)
{

}


int main()
{
    int arr[] = {30, 450, 14, 5};
    test(arr);
    return 0;
}

But this one works specifically because the C compilers should follow the C standard, which mandates that arrays "decay" into pointers when used as lvalues. Thus, a pointer to the array's first element is actually passed to test and everything works.

But the first code snippet is different. While an array name may decay into a pointer, the address of the array does not decay into a pointer to a pointer. And why should it? What sense does it make to treat an array so?

Pointers to pointers are sometimes passed to modify the pointers (simple pointer arguments don't work here because C passes by value, which would only allow to modify what's pointed, not the pointer itself). Here's some imaginary code (won't compile):

void test(int** p)
{
    *p = malloc ... /* retarget '*p' */
}


int main()
{
    int arr[] = {30, 450, 14, 5};
    int* ptr;

    /* Fine!
    ** test will retarget ptr, and its new value
    ** will appear after this call.
    */
    test(&ptr);

    /* Makes no sense!
    ** You cannot retarget 'arr', since it's a
    ** constant label created by the compiler.
    */
    test(&arr);

    return 0;
}

Pointers to arrays

Note that the original code could be modified a little to make it work:

void test(int (*p)[4])
{
    (*p)[2] = 10;
}


int main()
{
    int arr[] = {30, 450, 14, 5};

    test(&arr);
    printf("%d\n", arr[2]);

    return 0;
}

What is that weird type test accepts now? Say hello to a "pointer to array", one of the useless features of C. This is what the C FAQ has to say about it:

2.12: How do I declare a pointer to an array?

Usually, you don't want to. When people speak casually of a pointer to an array, they usually mean a pointer to its first element.

Truly, I can't imagine why one would use a pointer to an array in real life. If you do a web search on the topic, most of what you find is people mistakingly calling the parameter of foo(int* p) "a pointer to array", which of course it isn't. It looks to me like the whole concept is just an artifact of C's declaration syntax.

While the test function from the previous snippet compiles and works, it isn't of much use, since it's much clearer to write:

void test(int* p)
{
    p[2] = 10;
}

...
...
/* then call */
test(arr);

The main use of pointers as function arguments is to either avoid passing whole structures by value, or to modify the object pointed by the pointers. Both are irrelevant needs for pointers to array. Here's a clarifying snippet:

int joe[] = {1, 2, 3, 4};

void test(int (*p)[4])
{
    /* Fine: assign to an element through the
    ** pointer.
    */
    (*p)[2] = 10;

    /* Works, but won't be reflected in the
    ** caller since p was passed by value.
    */
    p = &joe;

    /* Error: arrays can't be assigned.
    */
    *p = joe;
}

Arrays are not passed by value anyway, so a pointer to an array is useless for this purpose. Neither can arrays be modified, so that kills the second reason.


Comments

comments powered by Disqus