Search code examples
cpointersconstants

Should I set argument to pointer to const if it might be changed?


For example, given a function

void func(const int* a, int* b){
    *b = 2 + *a;
}

Should I emmit the const if func is often called like so: func(ptr, ptr) ?


Solution

  • Short answer:

    If the function is supposed to be called with the same pointer in both parameters, you should not use const. Otherwise, if the function does not support both parameters being the same and what a points at shouldn't be modified, the const should be there as part of "const correctness".


    Long answer:

    const does not guarantee that a pointer is an unique alias. Rather, de-referencing a const int* is allowed to be an alias to de-referencing an int* as one of the exceptions to the so-called "strict aliasing rule", see the list of exceptions below C17 6.5:

    An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
    ...

    • a qualified version of a type compatible with the effective type of the object

    In practice, check out this example:

    #include <stdio.h>
    
    void func(const int* a, int* b)
    {
        int tmp = *a;
        printf("%d\n", tmp);
    
        *b = 2 + *a;
    
        tmp = *a;
        printf("%d\n", tmp);
    }
    
    int main (void)
    {
        int x = 1;
        int* ptr=&x;
        func(ptr,ptr);
        printf("%d\n",*ptr);
    }
    

    Compiled with gcc 11.2 or clang 14.0.0 x86_64 -O3, this prints:

    1
    3
    3
    

    That is, the value in tmp was reloaded since the compiler couldn't assume that the lvalue *a was not modified by the previous code.

    If we change this code to:

    void func(const int* restrict a, int* b)
    

    Then the output turns into:

    1
    1
    3
    

    restrict is a contract between the compiler and the programmer that all access of what a points at goes through the pointer a. Since the compiler is now free to assume that a and b do not alias, it can optimize out the tmp = *a; line, since tmp already contains the value of *a.

    In this specific example the programmer lies to the compiler by calling the function as func(ptr,ptr) despite arguments being restricted and the unexpected result of printing 1 inside the function is a bug caused by the caller code.