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

What is the correct way to tell the compiler that I want a variable to be always stored in a register?

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

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

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

编辑:示例代码。

这是从环形缓冲区中截取的代码片段,用于说明问题的要点。兴趣点是 __LDREXW__STREXW__CLREX,它们都在 cmsis_gcc.h 中定义。 They are intrinsic functions of the ARM synchronization primitives.我用它们来实现无锁机制。

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 ...

}

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

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

注意:此代码在 "bare-metal" 硬件上运行,没有操作系统等

register 大多被 C++ 编译器视为提示,甚至在 1998 年批准第一个标准之前也是如此。而且,在很多情况下,编译器已经能够做一个显着的寄存器分配比程序员做得更好,所以它忽略了那个提示。

标准 C++ 中没有通用或可移植的方法(即与来自不同供应商和不同主机系统的编译器一起工作的方法)来确保将特定变量放在寄存器中。

对于某些编译器,可以使用内联汇编程序显式使用寄存器。这种方法的问题是内联汇编程序在编译器和主机之间有所不同(本质上,它是实现定义的)。一些现代编译器也足够积极,它们也优化了内联汇编器,因此甚至可以在内联汇编器中删除寄存器的使用。分析和转换通常相对简单(至少与其他类型的优化相比),因此即使优化设置较低也可能发生。

唯一可以确定的方法是检查输出汇编器以确定编译器针对您选择的设置(优化等)做了什么。

可能唯一可靠的方法是使用汇编程序编写代码,而不是使用 C++ 中的内联汇编程序。 (我不知道有任何汇编程序可以在很大程度上优化代码,但我也从来没有理由尝试找到一个)。根据定义,这在系统之间不可移植 - 汇编器通常依赖于机器。

你的选择

Previously I had assumed that the register keyword will do this for me, but apparently it doesn't.

还引发了另一个潜在的问题——您正试图强制使用不必要的寄存器。

如果您之前假设 register 关键字就足够了,并且您的程序似乎按要求运行,那么您很可能无需担心寄存器中是否有任何变量。

我的观点是,如果性能非常重要,足以证明将任何变量强制写入寄存器是合理的,那么您应该知道您的编译器正在将您选择的变量放入寄存器,而不是只是假设是这样。如果您只是假设这是真的,并且没有遇到困难,编译器很可能忽略了提示,并且无论如何都实现了代码所需的性能。

我建议您需要质疑并重新验证您的信念,即您的特定变量实际上需要存储在寄存器中。

如果没有关于您的设计要求的信息 - 以及通过大量测试证明编译代码在实际情况下不满足这些要求的证据 - 您需要使用寄存器的假设通常是错误的。

What is the correct way to tell the compiler that I want a variable to be always stored in a register?

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

注意:

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

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

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

但是,作为 扩展 GCC compiler enables you to put a variable in a specified register如果没有非常好的理由,我不建议使用那个(至少要确保在使用和不使用该功能的情况下对代码进行基准测试)。

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

On the Cortex-M4 the implemented exclusives reservation granule is the entire memory address range (quoted from Cortex-M4 TRM),

那么我建议使用 small extended assembler code (for GCC) or, if absolutely necessary, declare a variable in a specified register

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

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