c++gccrestrict

Uncertainty of GCC __restrict__ rules


I am uncertain if passing a __restrict__ pointer to another function is always breaking the rules of restrict aliasing (assuming that other function reads or writes to it) or if it is more subtle

I am aware that it is not standard C++ and instead an extension of C99 that GCC supports. GCC's documentation doesn't really cover my question much: https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html

CPP Reference has more notes regarding it (however talking about C since C99): https://en.cppreference.com/w/c/language/restrict

Notably the CPP Reference link states:

During each execution of a block in which a restricted pointer P is declared (typically each execution of a function body in which P is a function parameter), if some object that is accessible through P (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined:

Also states:

Assignment from one restricted pointer to another is undefined behavior, except when assigning from a pointer to an object in some outer block to a pointer in some inner block (including using a restricted pointer argument when calling a function with a restricted pointer parameter) or when returning from a function (and otherwise when the block of the from-pointer ended):

Below I made some code to cover some cases I can imagine:

#include <cstring>
#include <iostream>

class Foo {
public:
    void func1(char* __restrict__ ptr1) {
        // 1. Does passing ptr1 to another method break the rules of restrict?
        // I don't think it does because func2 only reads in its own scope
        func2(ptr1);

        // func3 writes
        func3(ptr1);

        ptr1 += 3;

        // EDITED: 4. Is this read guaranteed to see the change from func2?
        std::cout << ptr1 << std::endl;

        // 3. Overlapping region that func3 wrote to
        std::memcpy(ptr1, " overlap\0", 9);
    }
    void func2(char* const __restrict__ ptr2) { std::cout << ptr2 << std::endl; }
    void func3(char* const __restrict__ ptr3) {
        // 2. Does writing to ptr3 in this scope break the rules for ptr1 in
        // func1? I am uncertain of this because from the point of view of func1
        // the memory ptr1 points to changed without being accessed by ptr1
        std::memcpy(ptr3, "after\0", 6);
    }
};

int main() {
    char buf[100];
    std::memcpy(buf, "before\0", 7);
    std::cout << buf << std::endl;
    Foo foo;
    foo.func1(buf);
    std::cout << buf << std::endl;
    return 0;
}

I am not sure if among cases 1, 2, and 3:

EDITED: Will case 4 always see the change func2 caused?


Solution

  • The exact definition of the C restrict qualifier is in §6.7.4.2 of the ISO C23 standard. I am assuming that the GCC __restrict__ C++ extension is based on that definition.

    According to that definition, the following are the answers to your 4 questions:

    1. Passing the value of a pointer which has been declared with the __restrict__ qualifier to a function does not violate the restrictions of that qualifier, even if the function parameter is declared as __restrict__. The called function is also allowed to dereference the pointer parameter. So your posted code is well-defined.
    2. Since ptr3 is based on (i.e. depends on) the value of ptr1, the __restrict__ qualifier of ptr1 is not being violated. So your posted code is well-defined.
    3. The __restrict__ qualifier only imposes restrictions during the execution of the block in which it is declared. Since func3 is no longer running when the line std::memcpy(ptr1, " overlap\0", 9); in func1 is executed, the __restrict__ qualifier of ptr3 in func3 is not being violated. So your posted code is well-defined.
    4. The printing of the value of ptr2 is guaranteed to occur before the printing of the value of ptr1, because the behavior of your program is well-defined and there is a sequence point after every full-expression.