cundefined-behaviorrestrict

Why is saving a restricted pointer's value not giving undefined behavior?


I am programming a virtual machine and I've made a union of restricted pointers to iterate the script's instruction stream:

union Pointer {
    uint8_t *restrict UInt8Ptr;
    uint16_t *restrict UInt16Ptr;
    uint32_t *restrict UInt32Ptr;
    uint64_t *restrict UInt64Ptr;

    int8_t *restrict Int8Ptr;
    int16_t *restrict Int16Ptr;
    int32_t *restrict Int32Ptr;
    int64_t *restrict Int64Ptr;

    float *restrict FloatPtr;
    double *restrict DoublePtr;
    const char *restrict CStrPtr;
    void *restrict Ptr;
};

For the CALL opcode, I have the instruction pointer's value saved (indirectly) which, if I understand the usage of the "restrict" keyword, would cause undefined behavior.

    (--regs[regStk].SelfPtr)->Ptr = ip.Ptr; /* push rip */
    *--regs[regStk].SelfPtr = regs[regBase];    /* push rbp */
    regs[regBase] = regs[regStk];   /* mov rbp, rsp */

I should also say that in the RET opcode, the instruction pointer's value is restored.

    regs[regStk] = regs[regBase]; /* mov rsp, rbp */
    regs[regBase] = *regs[regStk].SelfPtr++; /* pop rbp */
    ip.Ptr = (*regs[regStk].SelfPtr++).Ptr; /* pop rip */

I've done many many tests and even used different compilers (GCC and clang v3.5 and clang v6.0) and this didn't seem to produce undefined behavior, why is that?

EDIT UPDATE:

The variables are both declared one a local block scope:

int32_t VM_Exec(struct VM *const restrict vm)
{
    if( !vm or !vm->CurrScript.Ptr ) {
        return ErrInstrBounds;
    }

    union Value *const restrict regs = vm->Regs; // <--
    union Pointer pc = (union Pointer){.UInt8Ptr = regs[regInstr].UCharPtr}; // <--

Solution

  • Restrict keyword is only taken into account if the higher levels of optimization. gcc -O2 & -O3 only.

    in your examples I do not see anything which can cause the problem as we do not how those arrays are declares and how used.

    Here you have an example - I break the contract with the compiler .

    unsigned p = 100;
    
    void foo1(void)
    {
        p++;
    }
    
    void foo(unsigned *restrict x)
    {
        printf("p = %u\n", *x);
        foo1();
        printf("p = %u\n", *x);
    }
    
    int main()
    {
        foo(&p);
    }
    

    and the result is (-O3)

     100
     100
    

    compiled with -O1

    100
    101
    

    Another example: -O3

    unsigned p = 100;
    
    void foo1(void)
    {
        p++;
    }
    
    void foo(unsigned *restrict x)
    {
        unsigned *restrict p1;
    
        p1 = x;
        printf("p = %u\n", *x);
        foo1();
        printf("p = %u\n", *x);
        *p1++;
        printf("p = %u\n", *x);
    }
    
    int main()
    {
        foo(&p);
    }
    

    and the result:

    p = 100
    p = 100
    p = 101