是啥阻止了这个 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 &gt;&gt;= 1; 更改为selection &lt;&lt;= 1;(这对算法没有意义),mapBits() 可以计算编译时间。

这里是code in Godbolt。

【问题讨论】:

您的赋值运算符是..const? mapBits 未在 constexpr 上下文中使用,因此不需要在编译时完成,直至优化器。 @Jarod42 赋值运算符实际上并没有改变对象的状态。它仅用于加载硬件BSRR 寄存器,它不是类的一部分。 离题:operator= 不分配任何东西也不返回 *this (PortSegment &amp;) 可以搞砸未来的维护者。现在这个赋值操作符是个谎言,这应该是一些有名字的函数来解释它的作用。 gcc 将您的代码编译为单个 movl 指令,可以追溯到 6.1 版。 【参考方案1】:

您已在 Godbolt 中将优化设置为 1 级!试试-O3 而不是-O1

【讨论】:

你是对的!我通常不习惯在嵌入式系统上使用高优化级别,所以我忽略了它。实际上,我曾尝试过-O2,但完全忘记了-O3。谢谢!

以上是关于是啥阻止了这个 constexpr 函数的编译时评估?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在编译时评估数组?

constexpr 对不变表达式的好处[重复]

constexpr 和函数体 = 删除:目的是啥?

在这个例子中如何理解constexpr?

将函数指针分配给 constexpr 结构中的 typedef 函数的正确 C++ 方法是啥?

是啥阻止了这个 rails/vue 项目部署到 heroku?