cpointersundefined-behaviorrestrict-qualifier

Is assigning to a restrict qualified pointer always undefined behavior?


My understanding of restrict qualified pointers in that no restrict qualified pointer may point to the same memory address as another pointer if both pointers are accessible within the same scope. (There are other restrictions as well which are not relevant here).

Does this mean that assigning to a restrict qualified pointer is always undefined behavior if the pointer is being assigned based on the value of another pointer, even if the other pointer is never dereferenced?

this is relevant for Windows programming where you would call the windows procedure multiple times.

LRESULT CALLBACK WndProc(HWND Window, UINT message, WPARAM wParam, LPARAM lParam)
{
    static struct StructType* restrict MyStruct = NULL;

    switch (message)
    {
    case WM_INIT:
        MyStruct = ((struct WindowProcPayload*)wParam)->MyStruct;
        break;
    /*
    * dereference MyStruct in other switch tags later, in subsequent calls
    */
}

Solution

  • The key part of the formal definition of restrict for a pointer P is:

    If L [an lvalue whose address is based on P] is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: … Every other lvalue used to access the value of X shall also have its address based on P.

    What this tells us is that, if you assign a value to a restrict-qualified pointer and use it to access an object, then only that pointer (and things depending on it, such as further assignments or calculations) should be used to access the object, within the block associated with the restrict. In other words, if you assign a value to a restrict-qualified pointer and do not use other pointers to access the relevant object(s), that is fine.1

    In fact, if you know p and q may point to elements in the same array, but you have a segment of code where you know you access different elements with them, you can tell the compiler this:

    /* Code here uses p and q freely. */
    …
    
    /* Start block where we access distinct elements. */
    {
        int * restrict pr = p, * restrict qr = q;
        /* Use pr and qr to access distinct elements. Do not use p or q. */
    }
    
    /* Resume free use of p and q. */
    

    That is fine, and it could give the compiler opportunities to optimize based on restrict.

    Footnote

    1 The standard does contain a restriction on merely assigning to a restrict-qualified pointer P, albeit only from another restrict-qualified pointer:

    If P is assigned the value of a pointer expression E that is based on another restricted pointer object P2, associated with block B2, then either the execution of B2 shall begin before the execution of B, or the execution of B2 shall end prior to the assignment.

    This is subject to the same condition as above: It applies only if there is some object that is accessed via P and that is modified. So a mere assignment of one restricted pointer to another is not enough to trigger this problem, but, of course, part of the point of using restricted pointers is to use them to access objects that are modified. In any case, that sentence means the behavior of this code is not defined:

    void foo(int * restrict p)
    {
        int * restrict q = p;
        *q = 3;
    }
    

    because the parameter p and the automatic variable q have the same associated block, the body of the function. This problem is easily avoided by removing restrict from q or by nesting it:

    void foo(int * restrict p)
    {
        {
            int * restrict q = p;
            *q = 3;
        }
    }
    

    In the former case, removing restrict from q, then q is still “based on” p, so both it and p may be used to access the same objects.

    I do not see the motivation for that sentence. I suspect it is for some issue where a restrict-qualified pointer originates inside a nested block and its value would be assigned to a restrict-qualified pointer in an outer block, and the compiler would have to track information about the original pointer outside of its associated block. If so, the sentence may not have intended to disqualify pointers in the same block. Asking about this sentence may be worthy of a question on its own.