如何知道变量是在寄存器中还是在堆栈中?

All*_*nzi 42 c++ cpu-registers

我正在阅读inline关于isocpp FAQ的这个问题,代码是给出的

void f()
{
  int x = /*...*/;
  int y = /*...*/;
  int z = /*...*/;
  // ...code that uses x, y and z...
  g(x, y, z);
  // ...more code that uses x, y and z...
 }
Run Code Online (Sandbox Code Playgroud)

然后它说

假设一个典型的C++实现具有寄存器和堆栈,寄存器和参数在调用之前就被写入堆栈g(),然后从堆栈内部g()读取参数 并再次读取以在g()返回时 恢复寄存器f().但是,这是一个很多不必要的阅读和写作的,尤其是当编译器能够使用寄存器变量的情况下x, yz:每个变量可以被写入两次(如寄存器,也可以作为一个参数)和出两次(在使用时g()和在返回期间恢复寄存器f()).

我很难理解上面的段落.我尝试列出我的问题如下:

  1. 对于计算机对驻留在主存储器中的某些数据执行某些操作,是否必须首先将数据加载到某些寄存器然后CPU才能对数据进行操作?(我知道这个问题与C++没有特别的关系,但理解这个问题将有助于理解C++的工作原理.)
  2. 我认为f()函数的功能与g(x, y, z)函数相同.x, y, z在调用之前如何进入g()寄存器,传入的参数g()是否在堆栈中?
  3. 怎么知道x, y, z将它们存储在寄存器中的声明?g()存储内部数据的位置,寄存器还是堆栈?

PS

当答案非常好时(例如,由@MatsPeterson,@ TheodorosChatzigiannakis和@superultranova提供的答案),我很难选择一个可接受的答案.我个人更喜欢@Potatoswatter的那个,因为答案提供了一些指导.

Pot*_*ter 39

不要太认真地对待这一段.它似乎做了过多的假设,然后进入过多的细节,这是不能真正推广的.

但是,你的问题非常好.

  1. 对于计算机对驻留在主存储器中的某些数据执行某些操作,是否必须首先将数据加载到某些寄存器然后CPU才能对数据进行操作?(我知道这个问题与C++没有特别的关系,但理解这个问题将有助于理解C++的工作原理.)

或多或少,一切都需要加载到寄存器中.大多数计算机围绕数据路径,连接寄存器的总线,算术电路和存储器层次结构的顶层组织.通常,在数据路径上广播的任何内容都使用寄存器进行标识.

您可能还记得RISC与CISC的重大争论.其中一个关键点是,如果不允许存储器直接连接到运算电路,计算机设计可以简单得多.

在现代计算机中,存在架构寄存器,其是诸如变量的编程构造,以及物理寄存器,其是实际电路.在根据架构寄存器生成程序时,编译器会进行大量繁重工作以跟踪物理寄存器.对于像x86这样的CISC指令集,这可能涉及生成将存储器中的操作数直接发送到算术运算的指令.但在幕后,它一直在注册.

底线:让编译器做它的事情.

  1. 我认为f()是一个与g(x,y,z)相同的函数.为什么在调用g()之前x,y,z在寄存器中,并且在g()中传递的参数在堆栈中?

每个平台都定义了C函数相互调用的方式.在寄存器中传递参数更有效.但是,存在权衡取舍,寄存器总数有限.较旧的ABI通常会牺牲效率来简化,并将它们全部放在堆栈中.

底线:这个例子是任意假设一个天真的ABI.

  1. 怎么知道x,y,z的声明会将它们存储在寄存器中?存储g()中的数据的位置,寄存器还是堆栈?

编译器倾向于使用寄存器来获取更频繁访问的值.示例中的任何内容都不需要使用堆栈.但是,不常访问的值将被放置在堆栈上以使更多寄存器可用.

只有当您获取变量的地址(例如&x通过引用或通过引用传递)并且该地址转义内联时,编译器才需要使用内存而不是寄存器.

底线:避免采取地址并无缘无故地传递/存储它们.

  • @Allanqunzi适合寄存器的东西通常不应该通过引用传递.标准库遵循此约定,用于指针,整数,迭代器,重载标记等.但是,内联允许编译器查看引用,因为无论如何都没有传递. (3认同)

Mat*_*son 15

完全取决于编译器(与处理器类型一起)是否将变量存储在存储器或寄存器[或在某些情况下存储多个寄存器](以及您给编译器的选项,假设它有选项来决定这样的事情 - 大多数"好"编译器都这样做.例如,LLVM/Clang编译器使用一个名为"mem2reg"的特定优化传递,它将变量从内存移动到寄存器.这样做的决定取决于变量的使用方式 - 例如,如果在某个时刻获取变量的地址,则需要在内存中.

其他编译器具有相似但不一定相同的功能.

此外,至少在具有某些可移植性外观的编译器中,还有一个针对实际目标的generatinc机器代码阶段,其中包含特定于目标的优化,这也可以将变量从内存移动到寄存器.

不能[不了解特定编译器如何工作]来确定代码中的变量是在寄存器中还是在内存中.人们可以猜到,但这样的猜测就像猜测其他"可预测的东西",比如看着窗外猜测它是否会在几个小时内下雨 - 这取决于你居住的地方,这可能是一个完全的随机猜测,或者说是可以预测的 - 一些热带国家,你可以根据每天下午下雨的时间设置你的手表,在其他国家,它很少下雨,在一些国家,比如在英格兰,你不可能知道超出"现在它正在[不]在这里下雨".

回答实际问题:

  1. 这取决于处理器.适当的RISC处理器(如ARM,MIPS,29K等)没有使用内存操作数的指令,除了加载和存储类型指令.因此,如果需要添加两个值,则需要将值加载到寄存器中,并对这些寄存器使用add操作.一些例如x86和68K允许两个操作数中的一个作为内存操作数,例如PDP-11和VAX具有"完全自由",无论你的操作数是在内存还是寄存器中,你都可以使用相同的指令,只需不同操作数的不同寻址模式.
  2. 你在这里的原始前提是错误的 - 不保证参数g在堆栈中.这只是众多选择中的一种.许多ABI(应用程序二进制接口,又称"调用约定")使用寄存器作为函数的前几个参数.因此,它再次依赖于编译器目标的编译器(在某种程度上)和处理器(远远超过哪个编译器)参数是在内存中还是在寄存器中.
  3. 同样,这是编译器做出的决定 - 它取决于处理器有多少寄存器,哪些可用,如果"释放"某些寄存器则成本是多少x,y以及z- 从"完全没有成本"到"相当一点" - 再次,取决于处理器型号和ABI.


Mar*_* A. 7

对于计算机对驻留在主存储器中的某些数据执行某些操作,是否必须首先将数据加载到某些寄存器然后CPU才能对数据进行操作?

甚至这句话也不总是正确的.对于你将要使用的所有平台来说,这可能都是正确的,但肯定会有另一种架构完全没有使用处理器寄存器.

但是,您的x86_64计算机确实如此.

我认为f()是一个与g(x,y,z)相同的函数.为什么在调用g()之前x,y,z在寄存器中,并且在g()中传递的参数在堆栈中?

怎么知道x,y,z的声明会将它们存储在寄存器中?存储g()中的数据的位置,寄存器还是堆栈?

对于编译代码的任何编译器和系统,无法唯一地回答这两个问题.它们甚至不能被视为理所当然,因为g参数可能不在堆栈中,这完全取决于我将在下面解释的几个概念.

首先,您应该了解所谓的调用约定,这些约定在其他方面定义了函数参数的传递方式(例如,压入堆栈,放置在寄存器中,或两者的混合).这不是C++标准强制执行的,调用约定是ABI的一部分,ABI是关于低级机器代码程序问题的更广泛的主题.

其次,寄存器分配(即在任何给定时间哪些变量实际加载到寄存器中)是一项复杂的任务和NP完全问题.编译器会尽力使用他们拥有的信息.通常,较少访问的变量被放在堆栈上,而更频繁访问的变量保存在寄存器上.因此,该部件Where the data inside g() is stored, register or stack?无法一劳永逸地回答,因为它取决于许多因素,包括压力.

更不用说编译器优化甚至可以消除对某些变量的需求.

最后,您链接的问题已经说明了

当然,您的里程可能会有所不同,并且有许多变量超出了此特定常见问题解答的范围,但上述内容可作为程序集成可能发生的各种事件的示例.

即您发布的段落做了一些假设来设置示例.这些只是假设,你应该这样对待它们.

作为一个小小的补充:关于功能的好处,inline我建议看看这个答案:https://stackoverflow.com/a/145952/1938163


Tho*_*ews 5

如果不查看汇编语言,您无法知道变量是在寄存器,堆栈,堆,全局内存还是其他地方.甲变量是一个抽象的概念.只要执行没有改变,就允许编译器使用它选择的寄存器或其他存储器.

还有另一个影响该主题的规则.如果将变量的地址存储到指针中,则该变量可能不会放入寄存器,因为寄存器没有地址.

变量存储还可以取决于编译器的优化设置.由于简化,变量可能会消失.不改变值的变量可以作为常量放入可执行文件中.