clanguage-lawyerstrict-aliasingmemory-pool

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


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.


Solution

  • 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.