const and Pointers

const is a keyword widely used in C and C++. Variable modified byconst cannot be a Lvalue of operator =, which means it can not be modified after it is initialized.

int main() {
    int a = 10;
    a = 20;
    const int b = 20;
    b = 30; // ERROR
    return 0;
}

const in C and C++

In C, the variable modified by const is called a constant variable, which means it is still a variable, not a constant. For example, it is invalid to declared array size with a const int variable.

void main() {
    const int a = 20;
    int array[a] = {};  // ERROR
}

Therefore, the only difference with a const is that it can not be appended to an assignment expression. However, its memory can be modified.

void main() {
    const int a = 20;
    int *p = (int*)&a;
    *p = 30;
    printf("%d %d %d", a, *p, *(&a));   // 30 30 30
}

In C++, the variable modified by const is regarded as a real constant, and has the same nature with a constant, for example can be used to initialize an array. Actually, all occurrences of const variables are replaced by the initial value of it in the compiling process. So the same code has different result in C++.

int main() {
    const int a = 20;
    int *p = (int*)&a;
    *p = 30;
    printf("%d %d %d", a, *p, *(&a));   // 20 30 20
}

Notice that *p does change the memory of a at runtime in this case, but a has been replaced at compile time, so this change has no effect.

If we initialize a const variable with a normal variable instead of ab immediate value, then the const variable acts as a constant variable, and no longer has the nature of constants.

int b = 20;
const int a = b;
int array[a] = {};  // ERROR

const and Pointers

const keyword ensures two things. The first one, which we have already known, is that a const variable cannot be a Lvalue, which means it cannot be modified directly. Besides, const also makes sure that the constant can not be modified indirectly, which means the memory address of a constant can not be exposed to a normal pointer nor reference.

int main() {
    const int a = 10;
    int *p = &a;    // ERROR
    return 0;
}

In order to fix this, we should also modified the pointer with const, telling the compiler that what the pointer points to can not be modified indirectly with dereference as well. But there are various ways in adding const to a pointer:

const int *p = &a;  // 1
int const* p = &a;  // 2
int *const p = &a;  // 3
const int *const p = &a;    // 4

Are there any differences? C++ syntax clarifies that const modifies the nearest type. In the first case, const works on int, which means that *p, what p points to, can not be changed with dereference. But p itself doesn't have any regulations, and it can be changed. In the second case, const also modifies int, so it has the same effect as the first one. In the third case, const modifies int *, which means p can not be changed, but what p points to can be changed by dereference. The last case has both const on int and int *, which means both p and what p points to can not be changes.

const int *p = &a;  // Can't modify a with dereference, but p can point to another object
int const* p = &a;  // Same as above
int *const p = &a;  // Can modify a with dereference, but p can't point to another object
const int *const p = &a;    // Can't modify a with dereference, and p can't point to another object

The most common error in using const is the mismatch in type conversion. Now suppose we have the following code, which of those statements will raise an error?

int a = 10;
const int *p1 = &a; // 1
int *const p2 = &a; // 2
int *p3 = p2;   // 3
int *p4 = p1;   // 4

In the first case, p1 has a type of const int *, and &a has a type of int *, this conversion is valid. We now knows that *p1, which is the content that p1 points to can not be modified, but there's nothing to do with a, because a is not a constant, there is no constraint in changing a's value. Now look at the second case, what is the type of p2, is it int *const ? Notice that if there is no * after const, then const should not be included into the type.

int *q1 = nullptr;
int *const q2 = nullptr;
cout << typeid(q1).name() << endl;  // int *
cout << typeid(q2).name() << endl;  // int *

In the above code, q1 and q2 both have the type int *. So in the second case, p2 has the type of int *, and &a has the type of int * as well, so it is a valid conversion. In the third case, p3 and p2 both have a type of int *, so this conversion is correct as well. In the last case, p4 has a type of int *, and p1 has a type of const int *, this is invalid! Remember that const makes sure that the constant can not be modified indirectly, so p4 has to be const as well.

int a = 10;
const int *p1 = &a; // Correct
int *const p2 = &a; // Correct
int *p3 = p2;   // Correct
int *q = p1;    // Wrong

In all, we have the following formula in type conversion of pointer:

(int*) a = (const int *) b  // Wrong
(const int*) a = (int*) b   // Correct

const and Double Pointers

Double pointer with const works in a similar way. Remember that const always works on the nearest type.

const int **q;  // **q can not be modified directly
int *const *q;  // *q can not be modified directly
int **const q;  // q can not be modified directly

Now let's look at the following code. q has the the type of const int **, and &p has the type of int **, it seems work according to our previous inference, since a is not a constant and doesn't have constraint, but why there's an error?

int main() {
    int a = 10;
    int *p = &a;
    const int **q = &p; // ERROR
    return 0;
}

It might seems confusing, but we can think about it in this way. Assume that the above code is correct, then *q has the type of const int *. If we have the following code, then it is a valid operation because &b also has the type of const int *.

const int b = 20;
*q = &b;

But wait! What q points to is another pointer p, so the statement above is the same as p = &b. Since p is a not a const pointer, now we expose the memory of a constant b to a normal pointer p! This violate the rule of const that there is no directly modification of a constant. So our code above is not correct. We can fix this in two ways, either mark p as const or add another const on q.

int main() {
    int a = 10;
    const int *p = &a;
    const int **q = &p;
    return 0;
}
int main() {
    int a = 10;
    int *p = &a;
    const int *const*q = &p;
    return 0;
}

In all, we have the following formula in type conversion of double pointer:

(*int **) a = (const int **) b  // Wrong
(const int**) a = (int **) b    // Wrong
(int**) a = (int *const *) b    // Wrong
(int *const *) a = (int**) b    // Correct

Last updated