告诉编译器我希望变量始终存储在寄存器中的正确方法是什么?

Ven*_*emo 5 c++ gcc arm inline-assembly cortex-m

阅读这个问题的答案时,我注意到它register在 C++17 中不再是有效的存储说明符。一些评论甚至表明编译器已经忽略register了一段时间。

我将 GCC 6.x 与 ARM Cortex-M MCU 一起使用,并且有一些带有内联汇编的代码,这些代码绝对需要在寄存器中有一个变量。以前我认为register关键字会为我做这件事,但显然它没有。

  • 在现代 C++ 中,确保编译器始终为给定变量使用寄存器的正确方法是什么?
  • 如果没有标准方法,是否有特定于 GCC 的方法来执行此操作?也许某种属性?还是编译器特定的关键字?

编辑:为什么我需要在寄存器中存储一些东西?
我正在使用 ARM LDREX/STREX指令实现无锁环形缓冲区。我需要将 ARMLDREX指令的结果存储在寄存器中,因为将其存储在内存中会破坏 Cortex-M 上的整个机制。

编辑:示例代码。

这是从环形缓冲区中截取的代码片段,用于说明问题的要点。兴趣点是__LDREXW__STREXW并且__CLREX都在 中定义cmsis_gcc.h它们是 ARM 同步原语的内在函数。我使用它们来实现无锁机制。

template<typename T, uint32_t maxCount>
class RingBuffer final {

    __attribute__((aligned(8)))
    T buffer[maxCount];
    uint32_t start;
    uint32_t end;

    bool pushBack(const T &item) {
        register uint32_t exclusiveEnd;
        register uint32_t oldEnd;

        do {
            // Load current end value exclusively
            exclusiveEnd = __LDREXW(&end);
            __DMB();

            // Remember old end value so that
            // we can store the item at that location
            oldEnd = exclusiveEnd;

            // Check if ring buffer is full
            if (isFull()) {
                __CLREX();
                __DMB();
                return false;
            }

            // Figure out correct new value
            if (exclusiveEnd == (maxCount - 1)) {
                exclusiveEnd = 0;
            }
            else {
                exclusiveEnd ++;
            }

            // Attempt to store new end value
        } while (0 != __STREXW(exclusiveEnd, &end));
        __CLREX();
        __DMB();

        // Store new item
        //memcpy(buffer + oldEnd, &item, sizeof(T));
        buffer[oldEnd] = item;
        return true;
    }

    // ... other methods ...

}
Run Code Online (Sandbox Code Playgroud)

为什么 LDREX结果必须存储在寄存器中:

在 Cortex-M4 上,实现的独占保留粒度是整个内存地址范围(引用自 Cortex-M4 TRM),这意味着如果存储LDREX结果的变量最终在内存中而不是寄存器中,那么以下操作STREX将始终失败。

注意:此代码在“裸机”硬件上运行,没有操作系统等。

Bas*_*tch 5

告诉编译器我希望变量始终存储在寄存器中的正确方法是什么?

你不能这样做(在可移植的标准 C++ 或 C 代码中)。您需要信任您的编译器,因此您甚至不应该这样做。

请注意:

  • 最近的 C 和 C++ 标准(例如 C11 或 C++14 或 C++17)没有以命令式方式谈论处理器寄存器,并且他们提到register关键字(在上个世纪)只是编译器的提示。

  • 有些处理器(至少在过去)甚至没有任何真正的程序员可访问的处理器寄存器。

  • 最重要的是,您应该相信编译器能够进行足够好的优化,并且在某些情况下,将值放入寄存器中并不是最好的性能(特别是,因为该寄存器可以更好地用于某些其他值)。

然而,作为扩展,GCC编译器允许您将变量放入指定的寄存器中。如果没有充分的理由,我不建议使用它(至少一定要在使用和不使用该功能的情况下对代码进行基准测试)。

您确实需要了解当前编译器在大多数情况下都比您能做的更好。在尝试手动优化之前,请务必对您的代码进行基准测试(例如使用g++ -O3适当的参数进行编译)。-mtune=对于性能敏感的例程,还应检查生成的汇编代码(例如使用g++ -O3 -fverbose-asm -S)。

在 Cortex-M4 上,实现的独占保留颗粒是整个内存地址范围(引用自 Cortex-M4 TRM),

然后我建议使用一个小的 扩展汇编代码(对于 GCC),或者如果绝对必要,在指定的寄存器中声明一个 变量

也许您还需要使用reg选项编译所有代码(包括任何使用的库,包括标准 C 和 C++ 库!)。-ffixed-

但我坚持认为:你需要比现在更信任你的编译器。您确定您找不到(并且可能从源代码配置和构建)最新的GCC(例如 GCC 7),它可以作为内置或其他方式启用您的低级同步机制吗?