Search code examples
cfreertosstrict-aliasing

Do FreeRTOS heap implementations violate C aliasing rules?


Looking at the code for heap 1 in FreeRTOS...

#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )

/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */
    extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
    static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

...we see that a heap is just an array of uint8_t objects.

But then, in its void* pvPortMalloc(size_t xWantedSize) function, it defines a uint8_t* called pucAlignedHeap, and a size_t called xNextFreeByte.

Our return value pvReturn is then defined in this block...

 /* Check there is enough room left for the allocation and. */
        if( ( xWantedSize > 0 ) &&                                /* valid size */
            ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
            ( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) ) /* Check for overflow. */
        {
            /* Return the next free byte then increment the index past this
             * block. */
            pvReturn = pucAlignedHeap + xNextFreeByte;
            xNextFreeByte += xWantedSize;
        }

...and is then expected to be used by the programmer to store whatever data they want:

//Some example:
my_struct* x = pvPortMalloc(sizeof(my_struct));

But since the underlying data type is an array of uint8_t, doesn't that mean that any real usage of the heap violates C's aliasing requirements?

And if that's true, then why are they allowed to violate these requirements without worrying about UB? FreeRTOS is hardly a small hobby project, so they must know what they're doing, and yet it surely looks like this is UB. Why can they do this, but I can't? They do not appear to have -fno-strict-aliasing defined, so I don't think it's that.


Solution

  • Because there are many tasks that would never require the ability to recycle storage to hold multiple unrelated kinds of objects within its lifetime, the C Standard does not require that all implementations support such recycling. The Standard allows implementations to extend the language by supporting usage patterns beyond those mandated, and any implementation which is suitable for tasks that would require recycling storage within its lifetime will necessarily extend the language in that fashion. The Standard waives jurisdiction over such things, however.

    In the language processed by clang and gcc when not using -fno-strict-aliasing, once any storage has been written via an lvalue of non-character type, that storage will have that as an Effective Type for "for that access and for subsequent accesses that do not modify the stored value". Because that phrase doesn't say "all subsequent accesses until the stored value is modified using some other type", there is no way to usefully change the Effective Type of storage once it has been written. The storage may be written using other types, but after storage is written using two or more incompatible non-character types, any attempt to read it using any non-character-type would be incompatible with at least one of the Effective Types written to the storage, and thus invoke UB.

    Thus, all code that repurposes storage for use as different types within its lifetime will violate the aliasing constraints given in the Standard unless it limits itself to using character-type reads. Programmers shouldn't jump through hoops to satisfy such constraints, however, because implementations that are suitable for tasks that would require reuse of storage will support such tasks regardless of whether or not the Standard would require that they do so. Unfortunately, the Standard offers no guidance as to what constructs should be supported, treating such support as a Quality of Implementation issue.