使用C++14及以上版本有关stm32 hal库的警告

10

我也在STM32社区论坛发布了同样的问题,但没有得到答案。

我正在使用启用C++14的stm32 HAL库进行项目开发,出现了以下警告,但我无法消除它:

../platform/stm32/l4/STM32L4xx_HAL_Driver/Inc/stm32l4xx_hal_rcc.h:735:57:

注意: 转换为 void 将不会访问类型为‘volatile uint32_t {aka volatile long unsigned int}’的对象 UNUSED(tmpreg); \

当调用__GPIOX_CLK_ENABLE()或__HAL_RCC_GPIOX_CLK_ENABLE时,就会出现以上问题。

是否有人能够在保持HAL源代码不变的情况下消除上述警告?

或者有什么建议吗?

当前警告级别为-Wall。

我已经在l4和f4系列代码中遇到了上述问题。

以下是示例代码:

int main(void)
{
    HAL_Init();

    __GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.Pin = GPIO_PIN_7;

    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);

    for (;;)
    {
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
        HAL_Delay(500);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);
        HAL_Delay(500);
    }
}
罪魁祸首是__GPIOB_CLK_ENABLE(),在ST驱动程序中会被扩展成以下形式。
#define __HAL_RCC_GPIOB_CLK_ENABLE()           do { \
                                                 __IO uint32_t tmpreg; \
                                                 SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN); \
                                                 /* Delay after an RCC peripheral clock enabling */ \
                                                 tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOBEN); \
                                                 UNUSED(tmpreg); \
                                               } while(0)

我的原始问题旨在找到一种解决方案,同时保留底层的ST驱动程序。一种可能的解决方案是直接访问寄存器,而不通过库提供的便捷宏。

提前感谢您。


这绝对不是“重复”。我建议您下载STM32 CubeMX HAL源代码并同时编译C++11和C++14版本。警告在C++14中变得明显,但在C++11中从未出现过。 - aep
我能够通过将任何volatile引用转换为void来重现这个问题,甚至可以在C++03中实现。所以这与编译器版本无关。你的调用代码中肯定有一些在C++14中表现不同的地方。请编辑你的问题,并附上一个包含产生警告的调用代码的MCVE - Lundin
我现在会重新打开这个问题,但我认为没有示例是无法回答的。很可能问题出在ST驱动程序上,但据我所知,这些驱动程序是用纯C编写的?参考资料来自哪里? - Lundin
@Lundin 我编辑了问题并提供了一个示例。很明显问题源于ST驱动程序。但我想知道有没有一种方法可以抑制警告,同时保持ST代码不变。 - aep
@Lundin 没有人提到引用,只有你和你链接的问题。这是一个不同的警告。 - followed Monica to Codidact
显示剩余5条评论
3个回答

10
问题在于-std=c++14改变了将volatile表达式强制转换为(void)的语义,并引入了一个明显的无条件警告,而ST的一位程序员则试图“三重保证”寄存器读取会发生。 UNUSED()宏的定义是:
#define UNUSED(x) ((void)(x))

并且__IO被定义为

#define     __IO    volatile

那么__HAL_RCC_GPIOB_CLK_ENABLE()的扩展将会是:

do {
    volatile uint32_t tmpreg;
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
    /* Delay after an RCC peripheral clock enabling */
    tmpreg = RCC->AHB2ENR & RCC_AHB2ENR_GPIOBEN;
    ((void)(tmpreg));
} while(0)

各种STM32勘误表建议延迟寄存器的读取和回读操作。在启用外设时,应考虑RCC外设时钟使能和有效外设使能之间的延迟,以便管理对寄存器的读/写操作。在启用外设时应该从相应的寄存器插入一个虚拟读取操作。
由于所有外设寄存器都被声明为volatile,因此只包含所需寄存器的简单表达式将通过相同的外设总线强制执行必要的等待状态进行回读,因此这就足够了。
do {
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN;
    /* Delay after an RCC peripheral clock enabling */
    RCC->AHB2ENR;
} while(0)

剩下的部分可能是为了解决一些有错误的编译器而进行过度设计的解决方案,但我还没有见过一个如此糟糕的编译器,会将volatile类型的表达式优化掉。

然而,存在一种特殊情况,即将一个volatile变量转换为(void),在C++14中,其语义显然已经发生了改变。

让我们来看一个简单的例子。

void x() {
    volatile int t;
    t=1;
    ((void)(t));
}

使用-O3 -mcpu=cortex-m4 -mthumb -Wall -x c++ -std=c++11调用的Arm gcc 7.2.1会将其编译为:

x():
  sub sp, sp, #8
  movs r3, #1
  str r3, [sp, #4]
  ldr r3, [sp, #4]
  add sp, sp, #8
  bx lr

使用-std=c++14编译相同的代码时,结果是这样的:

链接
x():
  sub sp, sp, #8
  movs r3, #1
  str r3, [sp, #4]
  add sp, sp, #8
  bx lr

...以及一个警告:

<source>: In function 'void x()':
<source>:5:13: warning: conversion to void will not access object of type 'volatile int'
     ((void)(t));
            ~^~

还要注意第二种情况中缺少的ldr指令。在使用C++14进行写操作后,变量不再被访问。

我的原始问题旨在找到解决方案,同时保留基础的ST驱动程序。一种可能的解决方案是使用直接寄存器访问,而不是通过库提供的便捷宏。

我建议继续避免使用库,个人认为HAL最好作为示例或实现建议的集合来处理。

*我找不到禁用它的方法。这并不意味着没有。


1
干得好!然而,禁用它的(实用)方法确切地是-std=c++11吗?OP必须询问自己是否比HAL和无警告构建更需要C++14。我想,如果你倾向于使用HAL,那么它对你的实用性远远大于C++14的功能。我同意HAL本身有很多批评,但如果您正在使用它以利用依赖于它的STM32Cube生态系统的高级中间件(USB堆栈、文件系统、RTOS等),那么对于许多人来说,这是最简单的路径。 - Clifford
感谢 @Clifford 提供详细的解释。我想,在这个阶段,我会像您提到的那样使用寄存器访问来启用时钟,并继续使用STM32 HAL完成其余工作。谢谢。 - aep
@aep:我认为你误将评论发给了我 - 这不是我的答案。如果你发现HAL的C++14兼容性存在问题,我敢打赌你会再次遇到它;使用某种混合方法不太可能避免这种情况。 - Clifford
@Clifford Yep。这是我的错。我应该把它寄给berendi。顺便说一句,我也认为我将来可能会遇到类似的情况。看看会怎样吧。 - aep
@berendi:感谢您详细的解释。目前我会使用寄存器访问来启用时钟,但很可能在以后遇到类似的情况(正如我对Clifford所提到的)。这就是为什么许多人建议坚持数据表并实现自己的驱动程序。再次感谢您的反馈。 - aep
2
这不是C++14的特性,而是GCC的一个bug!我提交了一个bug,因为gcc在计算括号中被丢弃的值表达式时没有进行评估,而[expr]/11.1明确规定该表达式应该被评估。 - Oliv

4

您可以提交代码到自己的存储库中,以解决这个问题并仍然使用c++14编译代码。

/* Workaround for the broken UNUSED macro */
#include "stm32f3xx_hal_def.h"
#undef UNUSED
#define UNUSED(x) ((void)((uint32_t)(x)))

在包含任何HAL头文件之前,需要添加这个。对我来说,将其放置在模块启用宏(即#define HAL_WWDG_MODULE_ENABLED行)之后,但实际的HAL头文件之前,在stm32f3xx_hal_conf.h文件中较为方便。
我更新了所有源文件,用#include "stm32f3xx_hal_conf.h"代替了单独的HAL头文件。
这样做是因为基于@berendi的优秀研究,警告来自volatile指示。通过先将值转换为不是volatile的内容,可以避开C++14标准中的新条款。

我认为这个宏定义可能更加友好地处理x的类型: #define UNUSED(x) ((void)(__typeof__(x))(x)) - Glenn
@Glenn 我不明白那是如何移除volatile资格限定的。 - user2501488
它并不会。我在宏中使用__typeof__仅是为了尊重传递给UNUSED的表达式x的类型。您提出的宏强制转换为uint32_t。编译器可能会对此转换产生错误,具体取决于您的编译器标志(例如,是否警告某些类型转换)。 - Glenn
@Glenn 如果你不移除 volatile 限定符,你将始终收到 OP 中显示的编译器警告。因此,你提出的宏并没有解决所问的问题。 - user2501488
我的理解是,对 void 的 C 风格转换会移除 volatile 限定符。也许我已经进入了未定义行为的领域?我们不需要使用像 const_cast 这样的东西。我在 STM HAL 中使用我描述的宏,在 C++14 中没有问题。 - Glenn

1

如@oliv在回复@berendi的答案中提到的那样,根本原因似乎是GCC中的一个bug,在较新的版本中已经修复。当我升级到“Version 9-2019-q4-major”工具链(GCC 9.2)时,警告消失了。

此外,对于STM32G0系列,ST更改了UNUSED的定义:

#define UNUSED(X) (void)X      /* To avoid gcc/g++ warnings */

这将使得编译器早期版本的警告消失。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接