l-value and r-value, stack and heap

6
我是一名高中生,在BlueJ环境下学习Java。
背景:
在讨论按值传递和按引用传递机制时,我的书使用了堆栈和堆这些术语,并指出内存中的每个单元(也称为变量)都有一个名称、L值和R值,其中L代表“定位器”或“位置”,R代表“读取”。名称用于标识单元,L值存储单元的地址,R值存储单元的实际值。对于原始数据类型,它存储实际值,而对于引用数据类型,它存储引用数据类型的地址,该地址引用或指向它。当调用带参数的函数时,实际参数的R值被复制到实际参数的R值中。对于原始数据类型,复制实际值,而对于引用数据类型,复制引用地址,因此,在前一种情况下,实际值没有改变,而在后一种情况下,实际值发生了改变。
我的问题:
现在,我决定在互联网上学习更多关于这方面的知识。我发现互联网上的讨论与我的书不一致。那里说l-value和r-value分别是赋值符号左侧和右侧的值。我很困惑。 l-value和r-value的实际含义是什么?我的书中的栈、堆(我需要一个简单易懂的答案)和内存单位是什么意思?我在这个网站上找到了很多关于栈和堆的问题,但由于它们非常技术化,我无法理解那些答案。另外,我想知道在哪里可以学到更多相关知识。
这是我的教科书页面:

enter image description here

enter image description here

enter image description here enter image description here


你能提供所说的“互联网讨论”的链接或摘录吗? - Chetan Kinger
2
Java没有真正的lvalue和rvalue概念。语言规范中唯一提到它们的地方是在这里,这里说:“变量(§4.12)(在C中,这将被称为lvalue)”。rvalue基本上只是一个不是变量的表达式,意味着您无法对其赋值(它是分配的“右侧”)。 - Andy Turner
Wikipedia的文章可能会有所帮助。至于堆栈与堆,不幸的是我认为它们无法在不涉及技术方面进行解释。至少不能以任何方式解释它们与变量的关系。 - RealSkeptic
你的书似乎在说一些非常奇怪的事情。你能提供一下书中实际的内容吗?而不是你对它的解释?同时,提供一份正确的书籍引用,这样我们就可以避免它了。 - user207421
@CKing,http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c - MrAP
@EJP,我已经上传了我的教材扫描版的页面。 - MrAP
2个回答

5
  1. 当术语“左值”和“右值”首次被创造出来时,l和r确实意味着左和右。也就是说,“左值”最初指的是赋值语句的左侧,而“右值”指的是赋值语句的右侧。然而,后来它们被修改为分别表示'定位器'和'读取',正如你的书所建议的那样。原因是,像C这样的编程语言有许多运算符(例如取地址运算符&),其中出现在运算符右侧的操作数仍然是一个左值。
  2. 是内存中的区域。栈用于存储局部变量和函数调用。堆用于存储对象。堆由应用程序的所有线程共享,而栈分配给每个线程。

我们可以说堆存储所有引用数据类型,而栈存储所有基本数据类型吗? - MrAP
@MrAP 不行。考虑包含原始成员的对象。 - user207421
@MrAP,正如EJP所说,如果原始类型被声明为实例变量,它们显然会存储在堆中包装对象所在的位置。但是,如果原始类型在方法体中声明,它们将存储在栈中。 - VHS
你能告诉我栈和堆属于哪个主题吗?(我猜是与内存相关的,比如内存结构) - MrAP
它们应该在书中属于“内存管理和分配”。但是为什么这很重要呢? - VHS

2

堆栈和堆:

堆栈

堆栈只是每个程序启动时具有的特定内存范围。当该程序运行时,CPU实际上会存储指向堆栈顶部的指针。 当调用函数时,由编译器生成的代码将值(参数的副本以及调用函数的代码的返回地址)写入到被此堆栈指针(SP)引用的内存中。然后它修改SP指向稍微远一点的位置,即参数之后的位置。

当您的函数返回时,它将返回值写入由SP指向的内存位置,然后跳转代码执行回调用函数的代码。该代码然后从SP位置复制返回值,并递减SP。

这个区域称为堆栈,因为当您声明局部变量或使用参数调用函数时,程序会将值复制到其中。
然后在从函数返回时,它会弹出参数和局部变量。

(这是理论上的工作原理。在实践中,编译器将写入指令以将值复制到CPU寄存器中,如果可以的话。还有返回值)。

堆只是程序分配的所有其他内存,通常通过系统调用(Linux中的brk)(由C中的malloc调用)来分配。程序可以有许多内存块,它要求操作系统为其分配。这些内存块(作为整体)称为堆。

在Java中:

  • 当您使用“new”关键字时,它会返回指向某些内存的指针,该内存是它要求操作系统提供的。

  • 当您不使用new声明变量时,编译后的代码将简单地使用顶部的堆栈内存区域中的现有内存,然后更改堆栈指针。

当您使用指针变量并将其分配给使用new ExampleObject()创建的对象时,实际上正在执行两件事。在这种情况下,指针(引用)变量将在堆栈指针位置创建。然后移动堆栈指针(增加8个字节,即指针值的大小),然后new()函数将从堆区域获取新的内存引用,然后该引用的值将被复制到局部指针变量中。

实际上,像Java这样的语言在执行程序时,已经由操作系统分配了一定大小的堆栈和内存(称为堆),只有当空间不足时才会请求更多内存。

阅读如何CPU工作,特别是它们有存储值的寄存器,其中之一是堆栈指针。此外,阅读它们如何执行加法和减法也很重要,因为它们通常不会将一个地址中的数与另一个地址中的数相加(例如在添加时)。如果您查看汇编指令(类似于Java字节码),它们更经常做的是:
例如,一个函数 int addnum(int a, int b) {return a+b;}
a. 从SP中加载数字,即SP指向的位置,到寄存器1中
b. 从SP之前的位置(SP-1)加载数字到寄存器2中
c. 调用Add CPU指令,将结果存储在寄存器R3中
d. 将R3值复制到SP+1中
可能看起来像这样:
像这样调用代码:(请注意,这些是虚构的示例CPU指令 - 它们对于每个CPU都不同,并且Java有自己的类似字节码。我只是用于举例STORESP => 写入堆栈,LOADSP => 从堆栈指针加载)
int x;
x =  addnum(9,6);

INCSP  +1   #allocate x at location SP and increment SP by 1

# start function call
# make 3 spaces,   for a, b, and b and return value
INCSP   +3       #add 3 to SP register
STORESP 9,0           # copy 9 value  to SP-0
STORESP 6,-1         # copy 6 value  to SP-1

JUMP addnum    # jump to executing the function code                

然后,就是函数本身。
LOADSP,0,R1       #copy from SP-0 (a) into reg 1
LOADSP,-1,R2     #copy from SP-1(b) into reg 2
ADDREG,R1,R2,R3 # add reg1 reg2 and store in R3

STORESP,R3,-2        #save the result to SP-2
RETURN

然后再次调用函数: 将结果存储在x中(复制SP-2)(到SP-3)。
LOADSP,-2,R1
STORESP,R1,-3

现在函数调用已经完成。因此,通过将SP减去3来丢弃为a和b分配的堆栈空间以及返回值。
ADDSP -3

现在结果存储在 'x' 中。

当然,这只是一个简化且不准确的例子,只是为了帮助理解。

但是,如果您能够了解这些低级别的东西是如何工作的,即使只是像添加两个数字这样的基本操作,那么它也将帮助您理解函数中参数是如何传递的以及堆栈概念的重要性。

祝好运!


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