了解低级抽象

Understanding low-level abstraction

我今年 Java 开始编程了。我理解高级概念并且感觉编程很舒服。

但是我似乎一直在问我所有这些在内部是如何工作的?我知道 Java 是一种高级语言,专门用于让程序员远离低级内容以减轻开发负担。

从本质上讲,我想更多地了解高级语言的内部功能(例如面向对象编程)。我很清楚 为什么 使用它们,但现在 如何 一切都在内部工作(内存分配等)。对象如何在内部呈现等

有人可以用一些关键字为我指明正确的方向,或者最好参考一些资料吗?学习像 C 或 C++ 这样的低级语言会有助于这个学习过程吗?

我一直推荐的一本好书是 Andrew S. Tanenbaum 的 Modern Operating Systems

它涵盖了您在编程时想知道的所有操作方法

还有 this 来自 SO 的话题。

从你问题的措辞来看,你的low级别还是很高的。

面向对象与语言的高低无关,它只是面向对象,可以进行面向对象的组装。它不是语言的东西,基本上任何语言都可以以面向对象的方式使用。

内存分配特定于管理内存的操作系统and/or。在高层次上真的没有什么复杂的。我有一个披萨,有 3 个人,我可以把披萨切成 3 片或 4 片或 8 片或其他任何东西,每个人可以分配一片,还有一些剩下的,他们可以回来分配更多。现在,在消费后释放披萨分配并不是我们想要想象的事情。但是思路是一样的,你有一些内存你想让一个程序borrow/take。你把它分开,不必大小均匀。您可能会提供各种尺寸的 1K、2K、4K、8K...1Meg 单位等以及这些单位的倍数。你创建了一个 table/chart 谁消费了什么,还有什么是免费的。什么时候还给你标记他们免费。老派的线性思维会使这变得困难,但 MMU(内存管理单元)使这变得容易。那是低级或低级思维。它们是地址转换器以及保护功能,以防止程序访问不属于它们的内存。

从内存分配的角度来看 MMU 的作用的一种简单方法是认为所有可用的 borrow/take 内存都以 0x1000 字节为单位。假设从地址 0x10000 开始,那么 0x10000、0x11000、0x12000 等等。那是实际内存端的物理地址。但是我们也可以有一个虚拟地址space。我可能会要求 0x3000 字节,并且可能会给出一个指针 0x20000000。当我访问 0x20000000 和 0x20000FFF 之间时,mmu 可能将该虚拟地址转换为物理地址 0x00007000 到 0x00007FFF。但是 0x20001000 到 0x20001FFF 可能会转换为物理 0x00004000 到 0x00004FFF。自然地 0x20002000 到其他一些物理地址。因此,如果有人分配 10 个块,另一个分配 3 个,管理该分配的软件可以将前 10 个物理块分配给第一个程序,然后将接下来的 3 个物理块分配给下一个,如果第一个释放,那么有人分配 7 个前 7 个物理块可以给那个新人给我们一张地图,其中包含前 7 个使用的、3 个免费的和 3 个在物理线性视图中使用的。如果有人现在分配 4 个,我们实际上可以给他们 3 个,最后再给他们一个,因为我们可以将它们映射到虚拟 space 中,这样他们就会觉得他们正在线性访问它们。

如果我有一份按字母顺序列出的学生名单,那并不意味着他们的宿舍号是线性匹配的。名单上按字母顺序排列的第 1 号学生不必住在 1 号宿舍。我有一个 table 将他们的名字映射到他们的宿舍。如果我们按字母顺序在列表中间添加一个学生,并不意味着我们必须打乱所有宿舍号,我们只需要 table。所以可以从字母列表中给某人 5 个名字来从事一个项目,这并不意味着他们在 5 个相邻的宿舍里,当需要与这五个学生中的每一个交谈时,我们可以使用 table 的名字到宿舍找他们。虚拟地址是按字母顺序排列的列表,物理地址是那些人住的宿舍。管理 tables 并且程序可以访问它认为是线性内存 space,但实际上只是散布的片段.您不必 "defrag" 分配和释放内存。没有mmu,会变得很乱。

高级语言避免的低级内容是处理器的细微差别。我可以开车经过并点一个汉堡,或者我可以去买面包、肉、泡菜、西红柿、生菜、番茄酱等,然后自己做饭 assemble 一个汉堡。高级语言中的 a = b + c 最终可能是一些内存 and/or 寄存器访问,以将一个或多个寄存器保存到堆栈中,这样您就可以释放寄存器以收集它们存储的那些值内存(如果尚未在所述寄存器中)执行操作,现在或以后根据需要将结果保存到内存中。打印或文件访问或网络或视频等系统调用,大量代码执行小的单独任务以构成整体。所有的砖块、木板、钉子和水泥,以及建造建筑物所需的所有东西,比如汉堡,可以只买某人(编译器)建造的房子,或者我可以买五亿多的工具和材料来建造那个房子并以正确的顺序组合这些材料。

高级语言也给你抽象。这是C,但我打赌你能理解它。

unsigned int fun ( unsigned int a, unsigned int b )
{
    return(a+b+7);
}

我可以把它编译成泡菜、生菜和面包的配料,连同把它们放在一起的刀具和煎锅:

00000000 <fun>:
   0:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
   4:   e28db000    add fp, sp, #0
   8:   e24dd00c    sub sp, sp, #12
   c:   e50b0008    str r0, [fp, #-8]
  10:   e50b100c    str r1, [fp, #-12]
  14:   e51b2008    ldr r2, [fp, #-8]
  18:   e51b300c    ldr r3, [fp, #-12]
  1c:   e0823003    add r3, r2, r3
  20:   e2833007    add r3, r3, #7
  24:   e1a00003    mov r0, r3
  28:   e24bd000    sub sp, fp, #0
  2c:   e49db004    pop {fp}        ; (ldr fp, [sp], #4)
  30:   e12fff1e    bx  lr

我可以成为效率更高的麦当劳,而不是油腻的勺子晚餐:

00000000 <fun>:
   0:   e2811007    add r1, r1, #7
   4:   e0810000    add r0, r1, r0
   8:   e12fff1e    bx  lr

或者我可以在完全不同的计算机上使用相同的代码:

00000000 <_fun>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   1d40 0006       mov 6(r5), r0
   8:   65c0 0007       add , r0
   c:   6d40 0004       add 4(r5), r0
  10:   1585            mov (sp)+, r5
  12:   0087            rts pc

是的,使用正确的工具(gnu 工作得很好),您可以轻松地使用 C/C++ 并开始查看上面的内容并尝试理解它。语言在为你做什么。当涉及到像 printf 或文件访问等系统调用时。应用程序调用其他代码链接的库函数,这些函数最终要求操作系统去完成该任务(使用您的信用卡而不是现金购买汉堡) ,收银员现在必须在一个盒子里刷卡,这个盒子会与世界上某个地方的银行通话,请为我做这笔交易,比打开抽屉和收银员处理它更重要)。添加几个数字通常不涉及操作系统,但是访问受控的或复杂的或共享的资源,如视频或磁盘等,你必须要求操作系统为你做这件事,这是特定于语言、编译器和操作系统的。

Java 和 python(早期的 Pascal 等)通过编译为未实际实现的机器代码或直接在硬件中实现 table 来抽象它。然后拥有一个平台并运行特定的虚拟机(用其他语言如 C 编写)读取那些 java 字节码然后执行该任务,其中一些任务是 push b、push c、add (a),还有一些正在去读一个文件。可以 disassemble 并查看 JAVA 在字节码级别生成什么,但使用编译语言更容易做到。

javiergarval 对 Tanenbaum 书籍或类似书籍的回答可能涵盖您最初在中间层(操作系统)之后的内容。但是取决于你想走多低,进入汇编语言然后进一步进入逻辑和总线。

你可以考虑这本书 代码:计算机硬件和软件的隐藏语言 佩措尔德。从另一个方向过来。