Is it possible to use a character array as a memory pool without violating strict aliasing?

238 Views Asked by At

I have a statically allocated array of chars. Can I reuse this array for storing different types without violating strict aliasing rule? I don't understand strict aliasing really well, but here's an example of a code that does what I want to do:

#include <stdio.h>

static char memory_pool[256 * 1024];

struct m1
{
    int f1;
    int f2;
};

struct m2
{
    long f1;
    long f2;
};

struct m3
{
    float f1;
    float f2;
    float f3;
};

int main()
{
    void *at;
    struct m1 *m1;
    struct m2 *m2;
    struct m3 *m3;

    at = &memory_pool[0];
    
    m1 = (struct m1 *)at;
    m1->f1 = 10;
    m1->f2 = 20;

    printf("m1->f1 = %d, m1->f2 = %d;\n", m1->f1, m1->f2);

    m2 = (struct m2 *)at;
    m2->f1 = 30L;
    m2->f2 = 40L;

    printf("m2->f1 = %ld, m2->f2 = %ld;\n", m2->f1, m2->f2);

    m3 = (struct m3 *)at;
    m3->f1 = 5.0;
    m3->f2 = 6.0;
    m3->f3 = 7.0;

    printf("m3->f1 = %f, m3->f2 = %f, m3->f3 = %f;\n", m3->f1, m3->f2, m3->f3);

    return 0;
}

I've compiled this code using gcc with -Wstrict-aliasing=3 -fstrict-aliasing, and it works as intended:

m1->f1 = 10, m1->f2 = 20;
m2->f1 = 30, m2->f2 = 40;
m3->f1 = 5.000000, m3->f2 = 6.000000, m3->f3 = 7.000000;

Is that code safe? Assume memory_pool is always large enough.

2

There are 2 best solutions below

1
On BEST ANSWER

Is it possible to use a character array as a memory pool without violating strict aliasing?

No. The rule in C 2018 6.5 7 says an object defined as array of char may be accessed as:

  1. a type compatible with array of char,
  2. a qualified version of a type compatible with array of char,
  3. a type that is the signed or unsigned type corresponding to array of char,
  4. a type that is the signed or unsigned type corresponding to array of char,
  5. an aggregate or union type that includes array of char among its members, or
  6. a character type.

3 and 4 are not possible for array of char; they apply only when the original type is an integer type. In your various examples with structures, the structures are not types compatible with array of char (nor are their members), ruling out 1 and 2. They do not include array of char among their members, ruling out 5. They are not character types, ruling out 6.

I've compiled this code using gcc with -Wstrict-aliasing=3 -fstrict-aliasing, and it works as intended:

The sample output shows that the code produced desired output in one test. This is not equivalent to showing it works as intended.

Is that code safe?

No. The code can be made safe in certain situations. First, declare it with appropriate alignment, such as static _Alignas(max_align_t) memory_pool[256 * 1024];. (max_align_t is defined in <stddef.h>.) That makes the pointer conversions partially defined.

Second, if you are using GCC or Clang and request -fno-strict-aliasing, the compiler provides an extension to the C language that relaxes C 2018 6.5 7. Alternatively, in some cases, it may be possible to deduce from knowledge of the compiler and linker design that your program will work even if 6.5 7 is violated: If the program is compiled in separate translation units, and the object modules contain no type information or no fancy link-time optimization is used, and no aliasing violation occurs in the translation unit that implements the memory pool, then there cannot be adverse consequences from violating 6.5 7 because no way exists for the C implementation to distinguish code that violates 6.5 7 in regard to the memory pool from code that does not. Additionally, you must know that the pointer conversions work as desired, that they effectively produce pointers to the same addresses (rather than merely intermediate data that can be converted back to the original pointer value but not directly used as a pointer to the same memory).

The deduction that there are no adverse consequences is fragile and should be used with care. For example, it is easy to accidentally violate 6.5 7 in the translation unit implementing the memory pool, as by storing a pointer in a freed memory block or by storing size information in a hidden header preceding an allocated block.

0
On

The Standard deliberately refrains from requiring that all implementations be suitable for low-level programming, but allows implementations intended for low-level programming to extend the language to support such usage by specifying their behaviors in more cases than mandated by the Standard. Even when using compilers designed for low-level programming, however, using a character array as a memory pool is generally not a good idea. For compatibility with the widest range of compilers and platforms, however, one should declare memory-pool objects as either an array of the type with the widest alignment, or a union containing a character array long with the type having the widest alignment, e.g.

 static uint64_t my_memory_pool_allocation[(MY_MEMORY_POOL_SIZE+7)/8];
 void *my_memory_pool_start = my_memory_pool_allocation;

or

 union
 {
   unsigned char bytes[MY_MEMORY_POOL_SIZE];
   double alignment_force;
 } my_memory_pool_allocation;
 void *my_memory_pool_start = my_memory_pool_allocation.bytes;

Note that clang and gcc may be configured to extend the language in a manner suitable for low-level programming by using the -fno-strict-aliasing flag, and commercial compilers can often support low-level concepts like memory pools even when using type-based aliasing, since they recognize pointer-type conversions as barriers to likely-erroneous type-based aliasing assumptions.

If a void* is initialized to the address of a static object whose symbol is used in no other context, I don't think any commonplace compiler is going to care about the type that was used for the initialization. Jumping through the hoops to follow the Standard here is a fool's errand. When not using -fno-strict-aliasing, neither clang nor gcc will handle all of the corner cases mandated by the Standard, and with -fno-strict-aliasing, and they'll extend the semantics of the language to allow memory pools to be used conveniently whether the Standard requires them to or not.