Badasahog
Badasahog

Reputation: 763

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
    */
}

Upvotes: 3

Views: 98

Answers (3)

Eric Postpischil
Eric Postpischil

Reputation: 223776

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.

Upvotes: 3

supercat
supercat

Reputation: 81247

Although the Standard has a section which claims to be the "formal" specification of restrict, the authors do not seem to have systematically considered all corner cases sufficiently to merit such a title. The semantics of restrict are clear in circumstances where a restrict-qualified pointer object is given a value when it enters scope, and murky in situations where values are stored into it at other times. I don't know of any compilers that pay attention to the semantics of the qualfier in situations where an object receives a value other than when it is created, except that any pointers that had been based on its initial value remain based upon it.

In the scenarios where a restrict pointer receives a value at initialization, the semantics could best be described as saying that for every object in existence, throughout the lifetime of the restrict-qualified object, at least one of the following must be true of every region of storage throughout the universe:

  1. Its value doesn't change through the lifetime of the restrict-qualified object.

  2. It is accessed exclusively via pointers that are based upon the pointer value used to initialize the object.

  3. It is never accessed via pointer that is based upon the pointer value used to initialized the object.

In your example, the pointer value used to initialize the object is a null pointer, so #3 would vacuously apply to all objects in the universe.

A slight complication is that if p is based upon some pointer expression v and q isn't, and some piece of code would only execute in cases where p and q are equal, the defintion of "based upon" will completely fall apart within that code, meaning that no pointers may be safely dereferenced at all within it. I don't think the Standard intended to forbid constructs like:

if (p==q)
  doSomething(p,p);  // A compiler should be able to see that both arguments refer
                     // to storage accessed via p.
else
  doSomething(p,q);  // A compiler may treat regions of storage written via either
                     // argument as disjoint from storage accessed via the other.

but both clang and gcc interpret the definition of "based upon" in a manner that would render such constructs unreliable.

Upvotes: 1

Andreas Wenzel
Andreas Wenzel

Reputation: 25326

The exact rules of the C restrict qualifier are specified in §6.7.4.2 of the ISO C23 standard.


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.

This statement is incorrect. For example, the following code is well-defined:

int func( int *restrict p )
{
    int *q = p;

    printf( "%d\n", *p );
    *q = 20;
}

Doing this is well-defined because the value of q is based on (i.e. depends on) the value of p.

However, if the situation were reversed so that the restricted pointer depended on the normal pointer, and both pointers were dereferenced, and at least one of them modified the referenced object, then it would be undefined behavior:

int func( int *p )
{
    int *restrict q = p;

    printf( "%d\n", *p );
    *q = 20;
}

It would also be undefined behavior if the value of both pointer values were independant, but point to the same object:

int func( int *restrict p, int *q )
{
    printf( "%d\n", *p );
    *q = 20;
}

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?

No.

The following code is well-defined:

int func( int *p )
{
    int *restrict q = p;

    printf( "%d\n", *q );
    *q = 20;
}

However, if the other pointer is also restricted, then the behavior is undefined:

int func( int *restrict p )
{
    int *restrict q = p;

    printf( "%d\n", *q );
    *q = 20;
}

But if the second pointer declaration occurred in a sub-block, then the behavior would be well-defined:

int func( int *restrict p )
{
    {
        int *restrict q = p;
        printf( "%d\n", *q );
        *q = 20;
    }
}

For the purpose of the restrict rule, if a pointer is declared in the parameter list of a function, it is equivalent to being declared in the outermost block of the function (unless the pointer is declared with the extern storage-class specifier).

However, when declaring a restricted pointer in a sub-block, in that sub-block, you are only allowed to dereference pointers based on the pointer declared in that sub-block, for example like this:

int func( int *restrict p )
{
    {
        int *restrict q = p;
        int *r = q;

        printf( "%d\n", *q );
        *r = 10;
    }
}

And not like this:

int func( int *restrict p )
{
    {
        int *restrict q = p;

        printf( "%d\n", *p );
        *p = 20;
    }
}

But if the referenced object is not being modified, then the behavior is also well-defined, even if different pointers are used:

int func( int *restrict p )
{
    {
        int *restrict q = p;

        printf( "%d\n", *p );
        printf( "%d\n", *q );
    }
}

Upvotes: 3

Related Questions