C语言中的"register"关键字是什么意思?

316
在C语言中,“register”关键字是什么作用?我看过它被用于优化,但在任何标准中都没有明确定义。它还有用吗?如果有的话,在什么情况下会使用它?
在C语言中,“register”关键字可以提示编译器将变量存储在CPU寄存器中,以便更快地访问它们并提高程序效率。然而,现代编译器通常可以根据上下文自行选择最佳存储位置,因此“register”关键字很少使用。

56
register 关键字在 C 语言中有什么作用?(原文提示被忽略) - bestsss
27
并不完全忽略。尝试获取“register”变量的地址。 - qrdl
5
你正在阅读的那段代码很老 - Colonel Panic
19个回答

4

我使用以下代码在QNX 6.5.0下测试了register关键字:

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

int main(int argc, char *argv[]) {
    uint64_t cps, cycle1, cycle2, ncycles;
    double sec;
    register int a=0, b = 1, c = 3, i;

    cycle1 = ClockCycles();

    for(i = 0; i < 100000000; i++)
        a = ((a + b + c) * c) / 2;

    cycle2 = ClockCycles();
    ncycles = cycle2 - cycle1;
    printf("%lld cycles elapsed\n", ncycles);

    cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
    printf("This system has %lld cycles per second\n", cps);
    sec = (double)ncycles/cps;
    printf("The cycles in seconds is %f\n", sec);

    return EXIT_SUCCESS;
}

我得到了以下结果:

-> 经过 807679611 个周期的流逝

-> 该系统每秒有 3300830000 个周期

-> 每秒的周期数约为 ~0.244600

现在没有寄存器 int:

int a=0, b = 1, c = 3, i;

我得到了:

-> 已经过去 1421694077 个周期

-> 这个系统每秒有 3300830000 个周期

-> 周期数以秒为单位约为 0.430700


3
在七十年代,C语言的开端时,register关键字被引入,以允许程序员向编译器提供提示,告诉它变量将被经常使用,并且最好将其值保存在处理器的内部寄存器中。
现在,优化器比程序员更有效地确定更可能保留在寄存器中的变量,并且优化器并不总是考虑程序员的提示。
因此,许多人错误地建议不要使用register关键字。
让我们看看为什么!
register关键字有一个相关的副作用:您不能引用(获取)寄存器类型变量的地址。
建议他人不要使用寄存器的人错误地将此视为额外的论据。
然而,仅仅知道无法取寄存器变量的地址,就可以让编译器(及其优化器)知道该变量的值不能通过指针间接修改。
当指令流在某一点时,如果寄存器变量的值被分配到处理器的寄存器中,并且自那以后该寄存器未被用于获取另一个变量的值,则编译器知道它不需要重新加载该变量在该寄存器中的值。这样可以避免昂贵的无用内存访问。
进行自己的测试,您将在最内部循环中获得显着的性能提升。 c_register_side_effect_performance_boost

2

使用register关键字告诉编译器,程序员认为这个变量会被写入/读取的次数足以证明它应该存储在为变量使用的少数寄存器之一中。从寄存器读取/写入通常更快,可以需要较小的操作码集。

现在,这已经不是很有用了,因为大多数编译器的优化器比您更擅长确定是否应该将寄存器用于该变量,以及持续多长时间。


2

使用gcc 9.3编译器输出汇编代码,没有使用优化标志(本回答中的所有内容都是指没有使用优化标志的标准编译):

#include <stdio.h>
int main(void) {
  int i = 3;
  i++;
  printf("%d", i);
  return 0;
}

.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 3
        add     DWORD PTR [rbp-4], 1
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave 
        ret

#include <stdio.h>
int main(void) {
  register int i = 3;
  i++;
  printf("%d", i);
  return 0;
}

.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 8
        mov     ebx, 3
        add     ebx, 1
        mov     esi, ebx
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

这会强制使用ebx进行计算,这意味着它需要被压入堆栈并在函数结束时恢复,因为它是被调用者保存的。 register 会产生更多的代码行以及1个内存写和1个内存读(尽管实际上,如果计算是在esi中完成的话,这可以被优化为0次R / W,这就是使用C ++的 const register发生的情况)。不使用register 会导致2次写入和1次读取(尽管在读取时会发生存储器加载转发)。这是因为该值必须直接存在并更新在堆栈上,以便地址(指针)可以读取到正确的值。register没有这个要求,也不能被指向。constregister基本上是volatile的相反,使用volatile将在文件和块作用域以及块作用域中覆盖const优化。const registerregister将产生相同的输出,因为在C语言的块作用域中,const什么也没做,所以只有register优化适用。
在clang中,register被忽略,但const优化仍然发生。

在gcc中,register关键字可以对生成的代码质量产生巨大影响,尤其是在没有优化的情况下。在某些情况下,使用register关键字可能会导致编译器在-O0时生成比在更高的优化设置下更有效的代码[例如,在-O0下,如果一个寄存器限定的对象在循环外被加载,gcc将把加载留在循环外,但在更高的优化设置下,它可能会用一个常量替换该对象,最终在循环内重新加载]。 - supercat

1

Register关键字告诉编译器将特定变量存储在CPU寄存器中,以便快速访问。从程序员的角度来看,register关键字用于程序中频繁使用的变量,以便编译器可以加速代码。虽然是否在CPU寄存器或主内存中保存变量取决于编译器。


1
在支持的 C 编译器上,它尝试优化代码,以便变量的值保存在实际的处理器寄存器中。

1

当启用全局寄存器分配优化(/Oe编译器标志)时,Microsoft的Visual C++编译器会忽略register关键字。

请参阅MSDN上的register Keyword


0

Register 表示编译器通过将特定变量存储在寄存器中而不是内存中来优化此代码。这是对编译器的请求,编译器可能会考虑或不考虑此请求。 您可以在某些变量被频繁访问的情况下使用此功能。 例如:循环。

还有一件事是,如果将变量声明为 register,则无法获取其地址,因为它未存储在内存中。它在 CPU 寄存器中分配。


0
register关键字是向编译器发出请求,指定的变量应该存储在处理器的寄存器中而不是内存中,以获得速度提升,主要是因为它将被频繁使用。编译器可能会忽略此请求。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接