为什么无法将寄存器变量声明为全局变量?

8
9个回答

26

理论上说,你可以将一个处理器寄存器分配给一个全局作用域变量 - 这个寄存器只需在整个程序的生命周期内一直保持分配给该变量。

然而,C编译器通常无法在编译阶段看到整个程序 - C标准是这样编写的,以便每个翻译单元(大致对应于每个.c文件)可以独立地编译(编译后的对象稍后链接成一个程序)。这就是为什么不允许全局作用域寄存器变量 - 当编译器正在编译b.c时,它无法知道在a.c中已经分配了一个全局变量到一个寄存器中(因此b.c中的函数必须保留该寄存器中的值)。


+1,是的,例如在特定的嵌入式系统中,将永久需要的数据保留在专用寄存器中是很明显的。纯 C 无法满足这种需求。 - Peter G.

12

实际上,GCC允许这样做。在全局范围内以如下形式进行声明:

register int foo asm ("r12");

为全局变量"foo"(在x86_64上)分配寄存器"r12"。这有许多限制,相应的手册页面可能是所有麻烦的全局寄存器变量的最佳参考:


请注意,这通常不是整体性能/吞吐量的好主意;特别是在x86上,您可以假定具有快速缓存。用例可能是为了最小化中断延迟(如果您从未调用保存/恢复r12并将其用于其他用途的库函数)。或者可能全局变量在特定代码库中被大量使用,让编译器保留其永久值在那里是有意义的。还要注意寄存器是线程本地的。 - Peter Cordes
还要注意的是寄存器是线程私有的,因此使用这个的多线程程序实际上将使用线程本地存储。 - Peter Cordes

5
因为这样做没有意义。全局变量在应用程序运行时一直存在。这么长时间肯定没有空闲的处理器寄存器;)

10
SPARC 架构有专用的全局寄存器,非常适合这种目的,因此我认为把它称为 "无意义" 是不正确的。更像是“对于编译器在不同架构上实现来说不切实际”。 - Graphics Noob
我认为这个答案没有多大意义。只要您不知道OA实际想做什么,就说它毫无意义是相当傲慢的。 - coltox
@GraphicsNoob:在大多数CPU上,所有寄存器都是“全局”的:当您进行函数调用时,没有一个自动隐藏(像SPARC使用其寄存器窗口一样,这就是为什么它甚至需要提到它的原因)。当然,出于效率原因,您会一直使用它们来完成不同的任务,因为没有足够的寄存器来专门分配给全局变量。(除了极少数情况,您可以在全局范围内使用GNU C register int foo asm("regname") 手动执行此操作。- https://gcc.gnu.org/onlinedocs/gcc/Global-Register-Variables.html 通常对性能来说不是一个好主意,但内核可能会这样做) - Peter Cordes

2

最初,寄存器变量是用来存储在处理器寄存器中的,但全局变量必须存储在数据或BSS段中才能从每个函数访问。今天,编译器不严格解释register存储类,因此它主要用于兼容性。


3
全局变量什么时候放在堆里了? - qrdl
1
-1 表示 register 关键字被忽略是不正确的说法。 - Jens Gustedt
@Jens:现代编译器中有哪些会关注 register 关键字的?据我所知,gcc 忽略它(gcc 也忽略 inline)。 - R.. GitHub STOP HELPING ICE
@R.:请看我的回答。但是在此期间,您也可以尝试在函数范围内使用 register int i = 0; int *a = &i;。另外对于 inline,您完全错了;它是C99中非常重要和有用的补充。 - Jens Gustedt
实际上,我表述不够精确。register关键字并不会强制编译器将对象放置在处理器寄存器中,但所有其他语义保持不变。 - Philipp
仅仅为了兼容性的原因吗?你在抽什么? - theReverseFlick

2
register 关键字的含义与其名称所暗示的不同,现在它与处理环境的寄存器没有多少关系。(尽管它可能曾经是为此选择的。)唯一约束使用使用 register 声明的变量的文本是:

一元 & 运算符的操作数必须是函数指示器、[] 或一元 * 运算符的结果,或指示了一个不是位域且未使用 register 存储类说明符声明的对象的 lvalue。

因此,它对自动变量(在函数中声明的变量)实施了限制,使得获取此类变量的地址是错误的。然后的想法是编译器可以以任何方式表示该变量,例如作为寄存器或立即汇编值等。作为程序员,您承诺不会获取它的地址。通常对于全局变量这并没有太多意义(它们无论如何都有一个地址)。

总结:

  • 不,register 关键字不被忽略。
  • 是的,如果您想符合标准,它只能用于堆栈变量。

1

寄存器这个词在C/C++中被用作请求编译器使用处理器寄存器作为变量。寄存器是CPU使用的一种变量,非常快速地访问,因为它不位于内存(RAM)中。寄存器的使用受体系结构和寄存器本身的大小限制(这意味着有些可能只是像内存指针,其他用于加载特殊的调试值等)。

C/C++使用的调用约定不使用通用寄存器(80x86架构中的EAX、EBX等)保存参数(但返回值存储在EAX中),因此您可以声明一个类似于寄存器的变量,使代码更快。

如果您要求将其设置为全局变量,则要求为所有代码和源文件保留该寄存器。这是不可能的,因此编译器会给出错误,或者将其简单地作为存储在内存中的普通变量。


在gcc中不是这样的。参数通过寄存器传递。 - dev1223

1
一些编译器提供了将寄存器永久地分配给变量的手段。然而,寄存器关键字是不够的。编译器决定为例程分配局部变量到寄存器中通常不需要与其他源模块协调(虽然有些开发系统在例程之间进行寄存器优化,但更常见的做法是定义调用约定,以便所有例程都被允许自由地修改某些寄存器(因此,调用方负责保存内容,如果在函数调用后它们还需要)但不能更改其他寄存器(因此,调用的例程负责保存和恢复寄存器的内容,如果这些寄存器在函数中需要)。 因此,链接器不需要考虑寄存器的使用情况。

这种方法对于局部寄存器变量很好,但对于全局寄存器变量来说则没有意义。要使全局寄存器变量有用,程序员通常必须告诉编译器哪个寄存器用于什么变量,并确保在编译所有模块时编译器知道这样的保留,即使那些不使用寄存器的模块也是如此。这对于嵌入式系统特别有用,尤其是对于被中断使用的变量,但是在系统中通常只允许有非常有限的数量(例如2个或更少)这样的变量。


0

那么我们现在都同意了吗?我们都认为将全局变量作为寄存器变量是一个非常糟糕的想法吗?如果原始的C定义没有禁止它,那可能是因为没有人认为会有人以这种方式实现它 - 尤其是在CISC时代。

此外:现代优化编译器比人类更擅长决定何时将变量保留在寄存器中。如果您的编译器无法做到这一点,那么您真的需要获得更好的编译器。


-6

因为它们在寄存器中。这是一个自相矛盾的说法。


5
为什么这是一个自相矛盾的说法?虽然这个想法可能有些愚蠢,但在理论上,如果机器拥有足够的寄存器来支持这样的操作,它是可以工作的。 - Avi
1
当然可以。'register'修饰符是向编译器提示变量在其生命周期内应该保存在寄存器中的一种方式。它的生命周期由语言定义为方法作用域:它只能应用于函数参数或块内部,而块只能出现在方法内部。全局变量超出了方法作用域,同样是根据定义。因此,这是一个自相矛盾的说法。 - user207421
2
@EJP:所以你对于“为什么C语言不包括全局寄存器变量”的回答是“因为C语言不包括全局寄存器变量”。这并没有什么启示性,是吧? - JeremyP
2
@EJP:我正在改变问题的措辞,但我没有改变意思。这并不是自相矛盾。唯一的自相矛盾之处在于,如果寄存器只有本地作用域,那么就会产生自相矛盾。在某些体系结构中可能是真的,但通常情况下并非如此。 - JeremyP
1
我不同意这些观点。我非常自信,如果你问丹尼斯·里奇他的理由是什么,他也会说这是一个自相矛盾的说法。请记住,他在PDP-11上只有8个寄存器,包括PC和SP。如果他想将其限制为编译单元,他本可以通过允许“静态”来实现。但他没有这样做。 - user207421
显示剩余13条评论

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