【问题标题】:How can there be so many register variables, with such a limited number of registers?寄存器数量这么有限,怎么会有这么多寄存器变量?
【发布时间】:2019-03-13 15:15:36
【问题描述】:

我在玩C,我意识到,如果我声明了一堆寄存器变量,这些值不会被覆盖吗?从我从汇编中可以看出,微处理器中没有大量的寄存器,不足以满足我创建的需求。 C 如何保留所有值?

【问题讨论】:

  • register 这只是对编译器的提示。满足与否取决于后者。

标签: c variables assembly microprocessors


【解决方案1】:

C 旨在允许编译器在解析函数时为其生成汇编代码,而不必读取整个函数、对其进行检查,然后再生成代码。一个已解析程序的编译器:

int test(void)
{
  int x=0,y=0;
  int *p = &y;

  while(x < 10)
  {
    x++;
    foo();
    x++;
    *p *= 3;
    x++;
    bar();
    ...

无法知道x 的值是否可以安全地保存在对foo 的调用和/或对*p 的操作的寄存器中,或者foo 是否有可能更改x 的值。

register 关键字的目的是有效地告诉编译器在函数调用或写入指针的操作之间将对象的值保存在寄存器中是安全的,即使它还没有看到代码可能对对象做的所有事情。即使在今天,如果将对象的地址传递给嵌套函数不违反约束,这种含义仍然很有用,但是允许编译器假设在使用命名对象左值的任何上下文中,所有操作都将涉及该命名对象-对象左值。如果一个对象的地址从未被采用,则不需要限定符来引发这样的假设,但如果一个对象的地址 被采用但没有在涉及该对象的冲突操作中持续存在,这样的限定符可以给出否则它不会有的编译器信息。

【讨论】:

    【解决方案2】:

    register 暗示编译器可以将变量保存在寄存器中。您不能强制编译器使用比目标架构上更多的寄存器,原因很明显,这是不可能的。


    在 C 中,register 关键字仅仅意味着不能获取变量的地址。这会阻止您执行任何会阻止编译器将其保存在寄存器中但不要求将其保存在寄存器中的任何事情。

    来自https://en.cppreference.com/w/c/language/storage_duration

    仅允许在块范围内声明的对象使用寄存器说​​明符,包括函数参数列表。它指示自动存储持续时间并且没有链接(这是这些声明的默认值),但另外提示优化器在可能的情况下将此变量的值存储在 CPU 寄存器中。不管这种优化是否发生,声明为 register 的变量不能用作地址运算符的参数,不能使用 alignas(自 C11 起),并且寄存器数组不能转换为指针。

    多年来它并没有真正做任何事情:优化编译器已经尽可能将 vars 保留在 regs 中。对于全局变量或已获取地址的变量,可能仅用于函数的一部分,如果无法优化变量,则将结果存储回内存。


    顺便说一句,register 在 C++ 中已被正式弃用,而 C++17 实际上将其从语言中删除。 https://en.cppreference.com/w/cpp/language/storage_duration.


    相关:GNU C 有 register int foo asm("eax");(或任何其他寄存器),但即使是在用于本地时作为 inline-asm 语句的操作数时,也仅保证有效果变量。在当前的 GCC 版本中,它确实会导致编译器将该寄存器用于变量,除非它需要跨函数调用或其他方式将其溢出/重新加载到堆栈内存。

    https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html

    但在 GNU C 中,您可以使用 全局 寄存器变量,其中一个寄存器专用于您程序的整个生命周期的全局变量,这会损害不使用该变量的代码的优化。这是一个有趣的选项,但不是您应该使用的选项。

    【讨论】:

      【解决方案3】:

      变量通常存储在堆栈中。也就是一块内存。一个变量的值通常被加载到一个寄存器中进行操作,如果要操作另一个变量,则将其移回堆栈(保存)。通常变量甚至没有加载到寄存器中,而是在堆栈上进行操作。

      【讨论】:

      • 在 x86 等允许大多数指令使用内存操作数的架构上禁用优化有时会发生这种情况。今天使用的大多数其他架构都是加载/存储机器,只有加载和存储指令可以访问内存。即使在 x86 上,普通编译器也会尽可能优化自动存储变量的堆栈空间。无论如何,这个答案甚至没有提到 register 关键字。您似乎只是在描述不使用 register 的代码如何在禁用优化的情况下进行编译。但是 IIRC,register 可以让 gcc -O0 在 regs 中保留一个 var。
      • @PeterCordes 是的,我不明白这个问题,因为我以前从未听说过“注册”变量这个词,并认为这只是他的措辞。我将不得不自己使用这种类型的变量,因为我从未使用过它。
      • 你不需要,关键字对现代优化编译器没有用。
      【解决方案4】:

      旧的编译器会为register变量分配尽可能多的寄存器(在某些情况下,这个数字是0)并在堆栈上分配剩余的变量。

      现代编译器通常会忽略register 关键字。他们采用复杂的寄存器分配器,自动将尽可能多的变量保存在寄存器中。

      您可以依赖的register 的唯一效果是,如果您尝试获取寄存器变量的地址,则会收到一条诊断消息。否则,寄存器变量的行为就像自动变量一样。

      【讨论】:

      • 第二段的出处?
      • @JobHunter69 例如here 是 LLVM 的参考。关于寄存器分配器的部分是在大学教授的。
      【解决方案5】:

      不要求所有使用register 声明的变量都必须保存在 CPU 寄存器中。

      这是 C 标准所说的:

      具有存储类的对象的标识符声明 说明符寄存器建议对对象的访问速度与 可能的。这些建议的有效程度是 实现定义。

      参考:ISO C11 N1570 draft,6.7.1 第 6 段。请注意,它甚至没有提到 CPU 寄存器。

      符合标准的编译器可以简单地忽略所有 register 关键字(除了对获取 register 对象的地址施加一些限制)。

      实际上,大多数编译器会简单地将尽可能多的register 变量放在 CPU 寄存器中。

      事实上,现代优化编译器可能比大多数程序员更擅长寄存器分配——尤其是因为他们可以在每次修改程序后重新编译程序时重新计算寄存器映射。

      如今的普遍看法是,register 关键字并没有带来太多好处。

      【讨论】:

        猜你喜欢
        • 2017-06-15
        • 2023-03-12
        • 1970-01-01
        • 1970-01-01
        • 2012-01-28
        • 1970-01-01
        • 2012-02-10
        • 1970-01-01
        • 2021-07-28
        相关资源
        最近更新 更多