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

时间:2017-06-23 12:50:10

标签: c++ gcc arm inline-assembly cortex-m

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

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

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

编辑:为什么我需要在寄存器中存储内容? 我正在使用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将始终失败。

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

2 个答案:

答案 0 :(得分:3)

  

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

您无法做到(使用便携式标准C ++或C代码)。您需要信任您的编译器,所以您甚至不想这样做。

请注意:

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

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

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

但是,作为扩展名GCC编译器可让您放置variable in a specified register我建议不要非常好的原因使用(至少确保使用和不使用该功能对您的代码进行基准测试)。

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

  

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

然后我建议使用 extended assembler代码(对于GCC)或如果绝对必要,则声明variable in a specified register

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

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

答案 1 :(得分:2)

register主要被C ++编译器视为提示,甚至在1998年批准的第一个标准之前。并且,在很多情况下,编译器能够做得更好寄存器分配比程序员,所以它忽略了这个提示。

标准C ++中没有通用或可移植的方式(即与不同供应商和不同主机系统的编译器配合使用),以确保将特定变量放在寄存器中。

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

唯一可以确定的方法是检查输出汇编程序,以确定编译器执行的操作,为您选择的设置(优化等)。

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

您的选择

  

之前我曾假设register关键字会为我做这件事,但显然不是。

还引发了另一个潜在的担忧 - 你试图不必要地强制使用寄存器。

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

我的观点是,如果性能足以证明强制将任何变量强制转换为寄存器,那么你应该已知你的编译器将你选择的变量放在寄存器中,而不仅仅是假设它是所以。如果您刚认为这是真的,并且没有遇到困难,那么编译器很可能忽略了提示,并且无论如何都要为您的代码实现所需的性能。

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

如果没有关于您的设计要求的信息 - 并且通过大量测试证明编译代码在实际场景中不符合这些要求 - 您需要使用寄存器的假设通常是错误的。