查看 FreeRTOS 中堆 1 的代码...
#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 */
...我们看到堆只是一个 uint8_t 对象的数组。
但是,在它的
void* pvPortMalloc(size_t xWantedSize)
函数中,它定义了一个名为uint8_t*
的pucAlignedHeap
,和一个名为size_t
的xNextFreeByte
。
我们的返回值
pvReturn
然后在这个块中定义...
/* 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;
}
...然后被程序员用来存储他们想要的任何数据:
//Some example:
my_struct* x = pvPortMalloc(sizeof(my_struct));
但是由于底层数据类型是
uint8_t
的数组,这是否意味着堆的任何实际使用都违反了 C 的别名要求?
如果这是真的,那为什么允许他们违反这些要求而不用担心 UB? FreeRTOS 算不上是一个小型的业余爱好项目,因此他们必须知道自己在做什么,但它看起来确实像是 UB。为什么他们可以做到这一点,而我却不能?他们似乎没有定义
-fno-strict-aliasing
,所以我不认为是那样。
为什么他们可以这样做,而我不能?
你可以……但它是有代价的。
C 标准定义了许多行为规则,任何符合标准的实现都必须遵守这些规则。
此外,C 标准将许多事情留给了实现。这称为实现定义的行为。引用 C 标准 20111 草案 N1570:
3.4.1 实现定义的行为 未指定的行为,其中每个实现都记录了如何做出选择
最重要的是,该标准具有未定义行为的概念。
3.4.3 未定义的行为 行为,在使用不可移植或错误的程序构造或错误数据时, 本国际标准对此没有要求
对于未定义的行为,注释说:
可能的未定义行为包括从完全忽略情况到不可预测 结果,以 在翻译或程序执行期间以文件化的方式表现 环境(有或没有诊断消息的发布),终止翻译或 执行(发布诊断消息)
现在,如果您愿意编写只能用于特定实现/平台/环境的 C 代码,您可以编写非标准兼容代码,只要目标实现定义了行为就可以正常工作。没问题。那里有很多代码可以做到这一点。
代价是您的代码不能用于任何实现。
顺便说一句:
问题中提到的具体代码使用
uint8_t
。通过这样做,代码仅限于用于支持uint8_t
的实现。正如 N1570 的“7.20.1.1 精确宽度整数类型”中所写,C 标准并不要求所有实现都实现该类型。