Strict aliasing violations in C and how to write conformant code?

Viewed 167

The code seen below is typical of what is seen in some arena implementations. One such example can be found here (blog article on an example impl.).

#include<stdio.h>
#include<stdint.h>
#include<stdalign.h>

struct thing {
  int a;
  int b;
};

char buffer[128];

int main () 
{
  
  uintptr_t p1 = (uintptr_t)buffer;
  if (p1 % alignof(struct thing)) return 1;

  struct thing *t1 = (void*)buffer;
  t1->a = 10;
  t1->b = 20;
  
  uintptr_t p2 = (uintptr_t)(buffer + sizeof(struct thing));
  if (p2 % alignof(struct thing)) return 1;

  struct thing *t2 = (void*)(buffer + sizeof(struct thing));
  t2->a = 30;
  t2->b = 40;
  

  printf("%d\n",t1->a);   
  printf("%d\n",t2->a);   

  return 0;
}

edited code: Made the program return 1 if any pointer lacks proper alignment

  1. Is this an instance of a strict aliasing violation, and ...
  2. Is the only way to place such structures in a buffer and to retrieve a safe to use pointer to the structure to do for example:

struct thing *t1 = memcpy(buffer,&((struct thing){10,20}),sizeof(struct thing));

3 Answers

Is this an instance of a strict aliasing violation

Yes. t1->a etc access the character array through a different type than the "effective type" (char).

Is the only way to place such structures in a buffer and to retrieve a safe to use pointer to the structure to do for example:

You can also create a union of a raw character array and the type you wish to convert to. Example:

typedef union
{
  struct thing t;
  char buf[128];
} strict_aliasing_hack;

...

strict_aliasing_hack thing t1 = *(strict_aliasing_hack*)buffer;

This is ok because strict_aliasing_hack is "an aggregate or union type that includes one a type compatible with the effective type of the object among its members" (C17 6.5/7).

Naturally, it is best to stay clear of fishy conversions like this entirely. For example the chunk of data returned from malloc has no effective type. So the original code is much better written as:

struct thing *t1 = malloc(128);

And now you can lvalue access *t1 in any way you like.

The problem is that per standard only dynamic memory can be used that way.

Clause 5 Expressions says (ref n1570 for C11):

§6 The effective type of an object for an access to its stored value is the declared type of the object, if any.

If you use:

void * buffer = malloc(128);

then buffer is guaranteed to have an alignment compatible with any standard type and has no declared type.

In that case you can safely store a thing object in buffer without triggering any strict aliasing violation. But in your example code, buffer has a declared type which is char. Whatever the alignment, using a different type is then a strict aliasing violation.

IMO memcpy is always the safest way. It will produce the optimized enough output for the particular platform.

typedef struct thing {
  int a;
  int b;
}thing;

int geta(const void *buff, const size_t offset)
{
    const unsigned char *chbuff = buff;
    thing t;

    memcpy(&t, chbuff + offset, sizeof(t));

    return t.a;
}

int geta1(const void *buff, const size_t offset)
{
    const unsigned char *chbuff = buff;
    int a;

    memcpy(&a, chbuff + offset + offsetof(thing, a), sizeof(a));

    return a;
}

https://godbolt.org/z/x8e96ezW9

Related