Is modifying an object through a pointer to const undefined behavior?

Viewed 203

Suppose we have an int variable referenced as const int*which in turn is aliased as int *. Is it clear from the Standard if modifying the variable through the int * pointer is undefined behavior or not?

As an illustration, consider the following code:

void increment(int* p) {
    (*p)++;
}

void call_increment(const int* p) {
    increment(p);
}

int main(void) {
    int x = 7;
    int* p = &x;

    call_increment(p);
}
2 Answers

Modifying an object through a pointer to const is ill-formed, not undefined behavior.
Fixing that by casting away the const is well-formed unless the object referred to is actually const.

Your code has a different problem:
You are discarding a const-qualifier when passing p from call_increment() to increment().

Any useful compiler will complain about that even without being prompted:

g++ -x c -std=c18 main.cpp && ./a.out
main.cpp: In function 'call_increment':
main.cpp:6:15: warning: passing argument 1 of 'increment' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
     increment(p);
               ^
main.cpp:1:21: note: expected 'int *' but argument is of type 'const int *'
 void increment(int* p) {
                ~~~~~^

Just heed it, and better ask for more with at least -Wall -Wextra.

The const qualifier in C specifies that an lvalue will not be used to modify an object, but generally says nothing about whether the object might be modified in other ways, including through non-const-qualified pointer derived from the lvalue in question.

Two notable cases where it does have implications about the object:

  1. If a top-level lvalue is declared const but not volatile, there would be no defined means by which its value can change, and the Standard would invite implementations to freely assume that it won't. If a top-level lvalue is const volatile, the Standard would not anticipate any particular means via which the value could change, but quality implementations should generally allow for the possibility that the value might be spontaneously changed by means they know nothing about.

  2. If a pointer to a const object is qualified restrict, any object observed via that pointer or any lvalue derived from an address stored therein must have the same value throughout the active lifetime of the pointer. Thus, given e.g.

    int test(int const *restrict p)
    {
      if (*p == 1)
      {
        doSomething(p);
        return *p;
      }
      else
        return 1;
    }
    

    a compiler would be allowed to generate code that returns 1 without reloading *p, but it would not be allowed to do so without the restrict qualifier. This would also be true of e.g.

    int test(int const *p)
    {
      int const * restrict pp = p;
      if (*pp == 1) // Restrict-qualified pointer used to observe value
      {
        doSomething(pp);
        return *pp;
      }
      else
        return 1;
    }
    

    but not

    int test(int const *p)
    {
      int const * restrict pp = p;
      if (*p == 1)
      {
        doSomething(pp);
        return *p;
      }
      else
        return 1;
    }
    

    If a copy of p had been previously stored in a global object and doSomething were to completely ignore pp, changes to *p would not affect any object accessed via pointer derived from pp.

If one wants to maximize optimizations by indicating that an object identified via pointer will never change, one should generally qualify the pointer as restrict in addition to identifying the object as const.

Related