Home > Software engineering >  Is void** an exception to strict aliasing rules?
Is void** an exception to strict aliasing rules?

Time:11-09

Basically, is this code legal when strict aliasing is enabled?

void f(int *pi) {
    void **pv = (void **) π
    *pv = NULL;
}

Here, we access an object of one type (int*) through a pointer of another type (pointer to void *), so I would say that it is indeed a strict-aliasing violation.

But a sample attempting to highlight the undefined behavior makes me doubt (even if it does not prove that it is legal).

First, if we alias int * and char *, we can get different values depending on the optimization level (so it is definitely a strict-aliasing violation):

#include <stdio.h>

static int v = 100;

void f(int **a, char **b) {
    *a = &v;
    *b = NULL;
    if (*a)
        // *b == *a (NULL)
        printf("Should never be printed: %i\n", **a);
}

int main() {
    int data = 5;
    int *a = &data;
    f(&a, (char **) &a);
    return 0;
}
$ gcc a.c && ./a.out
$ gcc -O2 -fno-strict-aliasing a.c && ./a.out
$ gcc -O2 a.c && ./a.out
Should never be printed: 100

But the very same sample with void ** instead of char ** does not exhibit the undefined behavior:

#include <stdio.h>

static int v = 100;

void f(int **a, void **b) {
    *a = &v;
    *b = NULL;
    if (*a)
        // *b == *a (NULL)
        printf("Should never be printed: %i\n", **a);
}

int main() {
    int data = 5;
    int *a = &data;
    f(&a, (void **) &a);
    return 0;
}
$ gcc a.c && ./a.out
$ gcc -O2 -fno-strict-aliasing a.c && ./a.out
$ gcc -O2 a.c && ./a.out

Is it just accidental? Or is there an explicit exception in the standard for void **?

Or maybe just the compilers handle void ** specifically because in practice (void **) &a is too common in the wild?

CodePudding user response:

Basically, is this code legal when strict aliasing is enabled?

No. The effective type of pi is int* but you lvalue access the pointer variable through a void*. De-referencing a pointer to give an access which doesn't correspond to the effective type of the object is a strict aliasing violation - with some exceptions, this isn't one.

In your second example, both parameters to the function are set to point at an object of effective type int* which is done here: f(&a, (char **) &a);. Therefore *b inside the function is indeed a strict aliasing violation, since you are using a char* type for the access.

In your third example you do the same but with a void*. This is also a strict aliasing violation. There is nothing special with void* or void** in this context.

Why your compilers exhibits a certain form of undefined behavior in some situations is not very meaningful to speculate about. Although void* must by definition be convertible to/from any other object pointer type, so they very likely have the representation internally, even though that's not an explicit requirement from the standard.

Also you are using -fno-strict-aliasing which turns off various pointer aliasing-based optimizations. If you wish to provoke strange and unexpected results, you shouldn't use that option.

CodePudding user response:

Yes, void * and char * are special.

Is void** an exception to strict aliasing rules?

You are not aliasing through the void ** type; you are aliasing through void *. In *pv = NULL, the type of *pv is void *.

Generally, the C standard allows different types of pointers to have different representations. They can even have different sizes. However, it requires some pointer types to have the same representations. C 2018 6.2.5 28 says [separated into bullet points by me for clarity]:

  • A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.49)
  • Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.
  • All pointers to structure types shall have the same representation and alignment requirements as each other.
  • All pointers to union types shall have the same representation and alignment requirements as each other.
  • Pointers to other types need not have the same representation or alignment requirements.

Footnote 49 says:

The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

A note is not part of the normative part of the standard. That is, it does not form a rule that implementations must conform to. However, the note appears to be telling us that, regardless of the formal rules, you should be able to use a void * in place of a char * in certain places and vice-versa. Stating that two things should be interchangeable looks like a rule. My interpretation is that the authors of this text intended void * and char * to be interchangeable, at least to some extent, but did not have formal wording suitable for putting into the normative part of the C standard. There are in fact defects in the C standard’s treatment of aliasing, such as this one, so the C standard really needs a rewriting of the rules.

So, although this is not a normative part of the standard, compiler developers may give it deference and support aliasing a char * with a void * and vice-versa. This could explain why you see aliasing with char * behaving as if it is supported while aliasing with int * does not.

  • Related