Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does strict aliasing apply when using pointers to struct members?

Does test_func the following snippet trigger undefined behavior under the strict aliasing rules when the two arguments partially overlap?

That is the second argument is a member of the first:

#include <stdio.h>

typedef struct
{
    //... Other fields
    int x;
    //... Other fields
} A;

int test_func(A *a, int *x)
{
    a->x = 0;
    *x = 1;
    return a->x;
}

int main()
{
    A a = {0};

    printf("%d\n", test_func(&a, &a.x));

    return 0;
}

Is the compiler allowed to think test_func will just return 0, based on the assumption that A* and int* will not alias? so the *x cannot overwrite the member?

like image 691
Calmarius Avatar asked Jan 05 '23 14:01

Calmarius


2 Answers

Strict aliasing refers to when a pointer is converted to another pointer type, after which the contents are accessed. Strict aliasing means that the involved pointed-at types must be compatible. That does not apply here.

There is however the term pointer aliasing, meaning that two pointers can refer to the same memory. The compiler is not allowed to assume that this is the case here. If it wants to do optimizations like those you describe, it would perhaps have to add machine code that compares the pointers with each other, to determine if they are the same or not. Which in itself would make the function slightly slower.

To help the compiler optimize such code, you can declare the pointers as restrict, which tells the compiler that the programmer guarantees that the pointers are not pointing at the same memory.

Your function compiled with gcc -O3 results in this machine code:

0x00402D09  mov    $0x1,%edx

Which basically means that the whole function was replaced (inlined) with "set a.x to 1".

But if I rewrite your function as

int test_func(A* restrict a, int* restrict x)
{
    a->x = 0;
    *x = 1;
    return a->x;
}

and compile with gcc -O3, it does return 0. Because I have now told the compiler that a->X and x do not point at the same memory, so it can assume that *x = 1; does not affect the result and skip the line *x = 1; or sequence it before the line a->x = 0;.

The optimized machine code of the restrict version actually skips the whole function call, since it knows that the value is already 0 as per your initialization.

This is of course a bug, but the programmer is to blame for it, for careless use of restrict.

like image 140
Lundin Avatar answered Jan 13 '23 13:01

Lundin


This is not a violation of strict aliasing. The strict aliasing rule says (simplified) that you can access the value of an object only using an lvalue expression of a compatible type. In this case, the object you're accessing is the member x of main's a variable. This member has type int. And the expression you use to access it (*x) also has type int. So there's no problem.

You may be confusing strict aliasing with restrict. If you had used the restrict keyword in the declaration of one of the pointer parameters, the code would be invalid because restrict prevents you from using different pointers to access the same object - but this is a different issue than strict aliasing.

like image 25
interjay Avatar answered Jan 13 '23 12:01

interjay