TheMeaningfulEngineer
TheMeaningfulEngineer

Reputation: 16319

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.

Upvotes: 11

Views: 3825

Answers (4)

TheMeaningfulEngineer
TheMeaningfulEngineer

Reputation: 16319

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.

Upvotes: 1

underscore_d
underscore_d

Reputation: 6791

The language disallows unsafe conversions, i.e. casting a pointer-to-const-object to a pointer-to-non-const object.

It doesn't care about safe conversions, i.e. those where you apply a more strict const qualification than the passed thing really has. For instance, where the object is really writable, but you pass it to a function that only needs to read it, there would be no purpose to complaining about that.

Your examples show the latter:

f_ptr_a_type1(&a);

This says 'Take a const view of this int that I declared non-const'

f_ptr_a_type2(&a);

This just says 'Here's a pointer to that same non-const int, and you will take a copy of that pointer, which will be const within the function body (the pointer, not the int)'

As for the one that errors:

f_ptr_ptr_a(ptr_ptr_a);

This results from what Some programmer dude said: the referred types are different, and whereas C/C++ will allow passing a pointer-to-const where a pointer-to-non-const is expected, it won't 'cascade' that conversion through more than one level of pointer, since that might be unsafe as Felix Palmen has since illustrated.

Upvotes: 1

user2371524
user2371524

Reputation:

A conversion from e.g. char * to const char * is always safe. Through the const char *, the data that's pointed to can't be modified, and that's it.

On the other hand, a conversion from char ** to const char ** can be unsafe, therefore it isn't allowed implicitly. Instead of explaining it, consider the following code:

void foo(const char **bar)
{
    const char *str = "test string";
    *bar = str; // perfectly legal
}

int main(void)
{
    char *teststr[] = {0};
    foo((const char **)teststr);
    // now teststr points to a `const char *`!

    *teststr[0] = 'x'; // <- attempt to modify read-only memory
                       //    ok in this line, there's no const qualifier on teststr!
}

If the conversion from char ** to const char ** when calling foo() would be implicit, you would have an implicit way of converting a const away.

Upvotes: 15

Some programmer dude
Some programmer dude

Reputation: 409136

Lets break it down a little:

  • const int ** is a pointer to const int *.

  • int ** is a pointer to int *.

In other words, const int ** is a pointer to one thing, and int ** is a pointer to something different.

Upvotes: -2

Related Questions