是啥阻止了这个 constexpr 函数的编译时评估?
Posted
技术标签:
【中文标题】是啥阻止了这个 constexpr 函数的编译时评估?【英文标题】:What is preventing compile time evaluation of this constexpr function?是什么阻止了这个 constexpr 函数的编译时评估? 【发布时间】:2021-09-04 22:50:26 【问题描述】:我正在研究一个表示微控制器 (STM32) 的一组硬件引脚的类。选定的引脚在端口上可能不连续,但假定它们是有序的。例如,如果创建此 PortSegment
对象来表示 PA2、PA3 和 PA6 引脚,我希望能够进行类似 segment = 0b101u
的分配,它设置 PA2 和 PA6 并重置 PA3。
目前我还没有为不连续的引脚实现 ctor。当前一个只允许表示像 PA2、P3 和 PA4 这样的连续引脚。但是,将压缩位(如上例中的0b101u
)映射到实际硬件位的逻辑是针对不连续情况实现的。
我认为像segment = 0b101u
这样的赋值可以主要在编译时计算,并且只有在运行时加载实际的硬件寄存器(STM32 的BSRR
,它处理硬件引脚的原子设置和重置),使用预先计算的值。不幸的是,这不是发生的情况,要加载到 BSRR
的值也是在运行时计算的。
这是我正在测试的代码的稍微简化和半生不熟的版本。端口选择(GPIOA、GPIOB 等)代码被省略。
#include <cstdint>
volatile uint32_t BSRR 0; // Assume it's a HW register for atomic pin access.
class PortSegment
public:
constexpr PortSegment(uint8_t start, uint8_t end)
: selectioncalculateSelection(start, end)
uint16_t operator=(uint16_t setVal) const;
// operator uint16_t() const; // to be implemented later
private:
static constexpr uint16_t calculateSelection(uint8_t start, uint8_t end);
static constexpr uint16_t mapBits(uint16_t val, uint16_t selection);
uint16_t selection; // Table of used bits in the port
;
// Used in ctor
constexpr uint16_t PortSegment::calculateSelection(uint8_t start, uint8_t end)
uint16_t result 0;
for (unsigned i = start; i <= end; ++i) result |= (1u << i);
return result;
// static function
constexpr uint16_t PortSegment::mapBits(uint16_t val, uint16_t selection)
uint16_t result 0;
for (unsigned i = 0; i < 16; ++i)
if (selection & 1u)
if (val & (1u << i))
result |= (1u << i);
else
val <<= 1;
selection >>= 1;
return result;
inline uint16_t PortSegment::operator=(uint16_t setVal) const
uint32_t mapped mapBits(setVal, selection);
BSRR = ((~mapped << 16) | mapped)
& ((static_cast<uint32_t>(selection) << 16) | selection);
return setVal;
int main()
constexpr PortSegment segment 2,5; // Use port pins 2,3,4,5
segment = 0b1010u;
selection
成员变量表示端口中使用的引脚。例如,0b111100
表示使用 PA2、PA3、PA4、PA5。问题是,mapBits()
函数在编译期间没有被评估。我也尝试使它成为非静态成员函数,但没有任何改变。按照我的逻辑,当PortSegment
类的segment
对象创建时,在编译时一切都是已知的,也可以知道要加载到BSRR
中的值。但我似乎遗漏了什么。
我发现另一个奇怪的事情是,如果我将mapBits()
函数中的selection >>= 1;
更改为selection <<= 1;
(这对算法没有意义),mapBits()
可以计算编译时间。
这里是code in Godbolt。
【问题讨论】:
您的赋值运算符是..const
?
mapBits
未在 constexpr 上下文中使用,因此不需要在编译时完成,直至优化器。
@Jarod42 赋值运算符实际上并没有改变对象的状态。它仅用于加载硬件BSRR
寄存器,它不是类的一部分。
离题:operator=
不分配任何东西也不返回 *this
(PortSegment &
) 可以搞砸未来的维护者。现在这个赋值操作符是个谎言,这应该是一些有名字的函数来解释它的作用。
gcc
将您的代码编译为单个 movl
指令,可以追溯到 6.1 版。
【参考方案1】:
您已在 Godbolt 中将优化设置为 1 级!试试-O3
而不是-O1
。
【讨论】:
你是对的!我通常不习惯在嵌入式系统上使用高优化级别,所以我忽略了它。实际上,我曾尝试过-O2,但完全忘记了-O3。谢谢!以上是关于是啥阻止了这个 constexpr 函数的编译时评估?的主要内容,如果未能解决你的问题,请参考以下文章