Restricted pointer questions
Asked Answered
N

2

5

I'm a little confused about the rules regarding restricted pointers. Maybe someone out there can help me out.

  1. Is it legal to define nested restricted pointers as follows:

    int* restrict a;
    int* restrict b;
    
    
    a = malloc(sizeof(int));
    
    
    // b = a; <-- assignment here is illegal, needs to happen in child block
    // *b = rand();
    
    
    while(1)
    {
        b = a;  // Is this legal?  Assuming 'b' is not modified outside the while() block
        *b = rand();
    }
    
  2. Is it legal to derive a restricted pointer value as follows:

    int* restrict c;
    int* restrict d;
    
    
    c = malloc(sizeof(int*)*101);
    d = c;
    
    
    for(int i = 0; i < 100; i++)
    {
        *d = i;
        d++;
    }
    
    
    c = d; // c is now set to the 101 element, is this legal assuming d isn't accessed?
    *c = rand();
    

Thanks! Andrew

Northward answered 27/9, 2010 at 3:31 Comment(0)
S
5

For reference, here's the restrict qualifier's rather convoluted definition (from C99 6.7.3.1 "Formal definition of restrict"):

Let D be a declaration of an ordinary identifier that provides a means of designating an object P as a restrict-qualified pointer to type T.

If D appears inside a block and does not have storage class extern, let B denote the block. If D appears in the list of parameter declarations of a function definition, let B denote the associated block. Otherwise, let B denote the block of main (or the block of whatever function is called at program startup in a freestanding environment).

In what follows, a pointer expression E is said to be based on object P if (at some sequence point in the execution of B prior to the evaluation of E) modifying P to point to a copy of the array object into which it formerly pointed would change the value of E. Note that "based" is defined only for expressions with pointer types.

During each execution of B, let L be any lvalue that has &L based on P. If L 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: T shall not be const-qualified. Every other lvalue used to access the value of X shall also have its address based on P. Every access that modifies X shall be considered also to modify P, for the purposes of this subclause. 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. If these requirements are not met, then the behavior is undefined.

Here an execution of B means that portion of the execution of the program that would correspond to the lifetime of an object with scalar type and automatic storage duration associated with B.

My reading of the above means that in your first question, a cannot be assigned to b, even inside a "child" block - the result is undefined. Such an assignment could be made if b were declared in that 'sub-block', but since b is declared at the same scope as a, the assignment cannot be made.

For question 2, the assignments between c and d also result in undefined behavior (in both cases).

The relevant bit from the standard (for both questions) is:

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.

Since the restricted pointers are associated with the same block, it's not possible for block B2 to begin before the execution of B, or for B2 to end prior to the assignment (since B and B2 are the same block).

The standard gives an example that makes this pretty clear (I think - the clarity of the restrict definition's 4 short paragraphs is on par with C++'s name resolution rules):

EXAMPLE 4:

The rule limiting assignments between restricted pointers does not distinguish between a function call and an equivalent nested block. With one exception, only "outer-to-inner" assignments between restricted pointers declared in nested blocks have defined behavior.

{
    int * restrict p1;
    int * restrict q1;

    p1 = q1; //  undefined behavior

    {
        int * restrict p2 = p1; //  valid
        int * restrict q2 = q1; //  valid
        p1 = q2; //  undefined behavior
        p2 = q2; //  undefined behavior
    }
}
Slovene answered 27/9, 2010 at 4:5 Comment(0)
C
1

The restrict type qualifier is an indication to the compiler that, if the memory addressed by the restrict-qualified pointer is modified, no other pointer will access that same memory. The compiler may choose to optimize code involving restrict-qualified pointers in a way that might otherwise result in incorrect behavior. It is the responsibility of the programmer to ensure that restrict-qualified pointers are used as they were intended to be used. Otherwise, undefined behavior may result. (link)

As you can see from the above description, both your assignments are illegal, that may work in executables produced by some compilers but break in others. Don't expect the compiler itself to emit errors or warnings as restrict just gives an opportunity to perform certain optimization, which it can choose not to perform, like in the case of volatile.

Colander answered 27/9, 2010 at 4:5 Comment(5)
I don't think compilers are free to ignore volatile.Slovene
Compilers are generally free to ignore volatile if the implementation does not support any asynchronous signals (and thus signal handlers) which could alter the value of a variable outside the normal program flow, or other implementation-defined functionality (like threads or memory-mapped device IO) where volatile might make a difference.Strawn
@R: I believe you are mistaken, as the implementation cannot 'not support memory-mapped device I/O - that kind of I/O simply exists regardless of the compiler...Sunbeam
@einpoklum: Some processors use special instructions (often with its own dedicated address space) for the purpose of I/O, and do not have anything other than memory in any part of the address space that pointers can reach. On such instructions, I/O is typically implemented either via compiler intrinsic or by linking to code written in another language. Most 8031 variants, for example, have a 128-byte address space for I/O which can only be accessed via direct-mode instructions; there are no instructions that would allow code to write to an I/O register identified by a pointer.Gadroon
The comment by @Sunbeam and the upvote for it demonstrate the difference between C programmers that grok programming outside a typical "hosted" implementation on a typical operating system, and those who do not. There might be compilers out there which assume their target to be a bare-metal single-core single-threaded simple processor, where there is no OS, no context switches, no jumping into interrupt handlers, and no IO, unless you write your own code to manually do those things as part of normal well-defined C control flow.Felipafelipe

© 2022 - 2024 — McMap. All rights reserved.