cpointersconstants

const causing incompatible pointer type. Why only for double pointers?


This questions has been addressed here.

The suggested duplicate and the currently given answers don't address why there aren't issues with the examples given first. Mainly why doesn't the reasoning:

"const int ** is a pointer to const int * which is a different thing from just int*"

also apply for:

"const int * is a pointer to const int which is a different thing from just int"


I am approaching it from a different angle to hopefully get another explanation.

The code with the examples.

#include <stdio.h>

void f_a (int const a){

    /*
     *  Can't do:
     *      a = 3;  //error: assignment of read-only parameter ‘a’
     *
     * Explanation: I can't change the value of a in the scope of the function due to the const
    */
    printf("%d\n", a);
}

void f_ptr_a_type1 (int const * ptr_a){
    /*
     * Can do this:
     *     ptr_a’ = 0x3;
     * which make dereferencig to a impossible.
     *     printf("%d\n", * ptr_a’);  -> segfault
     * But const won't forbid it.
     *
     *  Can't do:
     *      *ptr_a’ = 3;  //error: assignment of read-only parameter ‘* ptr_a’
     *
     * Explanation: I can't change the value of a by pointer dereferencing and addignment due to the int const
    */
}

void f_ptr_a_type2 (int * const ptr_a){
    /*
     * Can do this:
     *     *a = 3;
     *
     *  Can't do:
     *      ptr_a = 3;  //error: assignment of read-only parameter ‘ptr_a’
     *
     * Explanation: I can't change the value because the const is protecting the value of the pointer in the funcion scope
    */
}

void f_ptr_ptr_a (int const ** ptr_ptr_a){
    /*
     * Can do this:
     *     ptr_ptr_a = 3;
     *     * ptr_ptr_a = 0x3;
     *
     *  Can't do:
     *      ** ptr_ptr_a = 0x3;  //error: assignment of read-only parameter ‘**ptr_a’
     *
     * Explanation: Makes sense. Just follows the pattern from previous functions.
    */
}

int main()
{
    int a = 7;
    f_a(a);

    int * ptr_a = &a;
    f_ptr_a_type1(&a);
    f_ptr_a_type2(&a);

    int ** ptr_ptr_a = &ptr_a;
    f_ptr_ptr_a(ptr_ptr_a);  //warning: passing argument 1 of ‘f_ptr_ptr_a’ from incompatible pointer type [-Wincompatible-pointer-types]
}

The widely accepted answer goes something like this:

int ** isn't the same as const int** and you can't safely cast it

My question is why does the function suddenly care?

It didn't complain here that int isn't int const:

int a = 7;
f_a(a);

It didn't complain here because int * isn't neither int const * nor int * const:

int * ptr_a = &a;
f_ptr_a_type1(&a);
f_ptr_a_type2(&a);

But suddenly it starts complaining in the double pointer case.


Solution

  • Why does the function suddenly starts worrying about write permissions of something that is outside of her scope?

    It's not that the complaint comes from the point of view of the function parameter. The parameter will behave just as expected inside the function scope and isn't affected by what happens with the variable before it enters the scope of the function.

    void f_ptr_ptr_a (int const ** ptr_ptr_a){
        /*
         *  Can't do:
         *      ** ptr_ptr_a = 3;  //error: assignment of read-only parameter ‘**ptr_a’
        */
    }
    

    int const ** ptr_ptr_a enters the function scope being copied by value disallowing a change of ** ptr_ptr_a. The error has nothing to do with what the variables were like once they have been copied by value.

    The error arises due to an implicit cast happening during the function call. Dissecting the call f_ptr_ptr_a(ptr_ptr_a); we get:

    int const ** ptr_ptr_x = ptr_ptr_a;     //This line causes the warning
    f_ptr_ptr_a(ptr_ptr_x);
    

    Looking for explanations using this terminology and example?

    Let's now strip the example to the bare basics.

    int main()
    {
    int a = 3;
    int * ptr_a = &a;
    int ** ptr_ptr_a = &ptr_a;        // I promise here **ptr_ptr_a will always be the same
    
    
    int const b = 5;
    int const * ptr_b = &b;
    int const ** ptr_ptr_b = &ptr_b;
    
    ptr_ptr_b = ptr_ptr_a;                  // Warning here: -Wincompatible-pointer-types-discards-qualifiers
    printf("%d\n", ** ptr_ptr_b);           // Look at me, I've just changed the value of const ** int.
    
    ** ptr_ptr_a = 15;
    printf("%d\n", ** ptr_ptr_b);            // I did it again.
    
    }
    

    The compiler is warning us because of the implicit casting.

    int main()
    {
        int const a = 3;
        int const * ptr_a = &a;
        int const ** ptr_ptr_a = &ptr_a;        // I promise here **ptr_ptr_a will always be the same
    
        int const b = 5;
        int const * ptr_b = &b;
        int const ** ptr_ptr_b = &ptr_b;
    
        ptr_ptr_b = ptr_ptr_a;
        printf("%d\n", ** ptr_ptr_b);           // Look at me, I've just changed the value of const ** int.
                                                // And there were no warnings at all. Har har har har!
    }
    

    There is one conclusion I can take out from this question and the, from the current perspective, unnecessary complexities it introduces.

    Always keep in mind that accessing by dereferencing isn't the same thing as accessing directly.

    We saw it here. The const qualifier is actually doing what it should, preventing us from changing ** ptr_ptr_b through the mechanism of dereferencing. Sure, we have managed to apparently change the read only value, but that is just because we're asking to much from the poor const.

    Another example from sheu's answer from here

    const char c = 'A';
    char* ptr;
    const char** const_ptr = &ptr;  // <-- ILLEGAL, but what if this were legal?
    *const_ptr = &c;
    *ptr = 'B';  // <- you just assigned to "const char c" above.
    
    printf("%c \n", c);
    printf("%c \n", *ptr);
    

    When you say you just assigned to "const char c" above, that isn't true, it's just the reference abstraction getting out of hand.