非常规入门之一:通用编程语言技术之上下文隐患(三)

在上文中,引入函数与接口的概念以将机器特性以及相关底层细节进行隐藏,其优势在于使用户(程序员)不必时刻关心底层的硬件逻辑,从而在一定程度上提高开发人员的开发效率。但是机器特性与相关底层细节的隐藏并不意味着开发人员能够在抽象层面为所欲为,这反而要求必须关心机器配置和相关的代码的效率问题。

故而在接下来的文章中,会为大家带来在抽象层面进行功能开发需要注意的一些细节。同时,这一部分也是通用编程语言技术向上提供强大功能的基础。请勿将此“基础”与前两章相提并论。前两章内容是通用编程语言技术得以实现的基础。此处所指“向上”乃是面向应用、逻辑或抽象层。


一、由函数带来的上下文问题

在前文中提到,函数的调用过程依次为如下9个步骤。在本节中,我们的重点放在发生跳转的部分,即第12389步。

1、获取上下文,即函数名
2、跳转至函数所在地址
3、保护上下文环境,即将之前的调用位置保存在栈中
4、为局部数据开辟空间
5、执行函数体
6、将返回值保存在RAX寄存器中(可能有、可能没有)
7、释放局部数据的空间(开辟多少就释放多少)
8、预备恢复上下文环境,将保存在栈中的前一个调用位置取出。
9、恢复上下文环境,即跳转至前一个调用位置

在之前的文章中,我们提到标识符是有效数据存储在内存中物理地址的字符化表示,同时我们也将函数视为一种特殊的数据。忘记的小伙伴可以通过第一章“基础”的第五节中进行回顾。

函数名作为标识符进行识别,其在底层中也就是一个地址,只是在这个地址的内存空间中存储的是一段有意义的、能够执行的逻辑代码。同时,函数又可以称为“子程序”、“过程”,因而在汇编语言中,函数的章节中,一般会使用“子程序设计”或“过程调用”等类似的说法,我们只需知道函数就是子程序、就是过程。

当然,可能有小伙伴会将函数和计算机组成原理中的中断进行类比,虽然二者有一定的区别,但这种区别微乎其微,以至于基本没有什么比较好的例子来举证,所以也就可以把中断当成函数来理解。

在上文列举的步骤中,我们可以得知,函数正式调用之前,先获取了函数所在内存中的地址(即函数名)。此处会有若干个陷阱,那就是:1、这个函数所在的地址不存在;2、这个函数所在的地址上是空操作

下面进行分析:
1、函数所在地址不存在

在通用编程语言技术中,使用的语法都是高级语言语法,所以在编译时中就会将这种由用户(程序员)行为缺失导致的致命的语法错误识别出来。

如果编译功能比较弱,不进行语法错误识别(当然,这是不太可能的),就需要由底层语言负责错误识别;如果底层语言仍然无法识别,则有程序在运行时触发程序错误中断。

2、函数所在地址为空操作

若空操作由高级语言语法转换而来,则必然不会出现纯粹的空操作,在函数末尾会出现ret指令返回调用处;若纯空操作(汇编语言nop指令)是由用户(程序员)利用某些机制内嵌汇编语言形成的(必然不可能),则由底层语言进行优化;若底层语言无法进行优化(必然不会出现),则有程序在运行时触发程序运行时中断(由于程序运行无操作触发,应称之为“警告”,因为其并未造成程序的崩溃)。

故而可以得出以下结论:
1、函数的调用行为包含三个动作:获取函数地址,跳转,保存跳转前地址。
2、在第1点中,涉及到的两个地址称为上下文环境。
3、函数调用的三个动作分别称为调用前,调用时,调用后;“调用前”时刻的上下文就已经确定下一个上下文;“调用时”时刻,上下文发生变化;“调用后”时刻,上下文将一直保持到函数返回后。

当函数的内部逻辑处理完成后,应当还原到上一个上下文,但是,如果在函数调用后不进行保存,则整个程序的逻辑依旧会发生异常。所以,自高级语言诞生以来,函数调用的细节就始终交由编译器进行处理,上下文环境也由编译器增加的语句保存至栈中。故而伴随函数调用的行为就是环境保护。注意:保存的是前一个上下文,而不是调用后的当前上下文,因为当前上下文就在寄存器中,无需保存。

全局状态下理论上不需要环境保护,但如今计算机系统中,运行时(即进程和线程)不只一个,所以全局环境也会由编译器增加若干语句,以完成全局环境的保护。

函数完成后的返回行为又可称为上下文环境的恢复。包含两个动作:获取保存的上下文、返回。可能这里“返回”二字会使人满脸问号。事实上,在汇编语言中函数执行后的返回行为只包含pop和ret两个指令。pop用于将保存的上下文重新写入RBP寄存器,RET指令用于跳转至前一个上下文环境。

二、上下文环境的延伸:命名空间

命名空间是更高级、更复杂、动态的上下文环境,在命名空间中能够存储更为复杂的数据。

在前一节中,我们使用上下文环境可以得知函数之间的调用关系,很明显,这只能用于面向过程。如果我们需要向面向对象过渡,就需要知道当前函数所属于哪一个对象。

在上下文环境中,我们可以直接访问RBP寄存器和栈就可以得知上下文环境,那么命名空间呢?
很显然,我们无法直接获取命名空间,故而需要将其专门保存下来。实际上,如果你没有学过C++或者没有处理类似的问题,你可能永远也碰不到命名空间的概念。

命名空间是用来组织和重用代码的重要手段。这是一句废话。然而就是这句废话,却能够让“通用编程语言技术”实现从面向过程到面向对象的质性飞跃。

我们知道,一个类的不同对象的属性值可能不一样,但是对象使用的方法的内部逻辑都是一致的,那么这些方法存储在哪里呢?当然是全局区域。因为类的方法,不属于任何一个对象。但是类的方法可能不止一个,如果允许方法同名,则参数有可能不一样。如此该怎样确定是哪一个对象在使用哪一个方法呢?

首先,我们应该确定三条基本原则:
1、命名空间的名称应当符合标识符的正规式(或正则表达式,都是一个东西)
2、命名空间的名称在所在作用域中具有唯一性。
3、命名空间允许嵌套一个或多个命名空间。

根据以上三条原则,我们可以立即找到一个命名空间,即某一个程序的全局命名空间(namespace Global),当然进程之间是不会重名的,程序的运行由操作系统控制,程序一旦成功申请内存空间,操作系统就会为它分配一个有效的唯一的进程号,该进程号既可以用于操作系统管理进程的销毁、内存释放,同时还可以作为这个进程内部最顶层的命名空间。进程与进程之间是相对独立的。进程间通信则需要依赖操作系统提供的服务。

既然命名空间名称是一个标识符,也就意味着命名空间实际在机器内部并不存在,即命名空间也是一个有效的地址。

而至于命名空间在物理内存中存储格式,实际是需要根据底层设计进行实现的,这也就意味着不同平台可能实现的方式不一样,不同编程语言的实现方式也不尽相同。

至此,不知道有没有小伙伴发现一个问题:类和命名空间的作用如此类似!既然如此类似,那么类是不是特殊的命名空间呢?作者个人认为,类应该算是一种特殊的命名空间,但是类和命名空间仍然存在区别。

命名空间的作用看起来比类的作用要少很多。命名空间的作用就是用来组织和重用代码,但是类的功能远不止于此。其实也没有必要在命名空间和类之间纠结。

三、上下文的梦魇:上下文对象

随着更多的编程语言支持Lambda表达式(点名C++,其他高级语言早就支持了,C++11才支持),上下文对象的身影越来越多,即使只是用JavaScript,上下文对象也能迷惑一阵子。

Lambda表达式又称为闭包,其形式大致符合“参数列表=>内部逻辑”。但是由于不同语言的函数定义方式不同,这也导致不同语言的闭包格式也是千奇百怪。

/* C# */
(<参数列表>)=>{<语句>} // 只有一条语句可以省略大括号
/* Java */
(<参数列表>)->{<语句>} // 只有一个参数可以省略小括号
/* C++ */
[<传值类型>](<参数列表>){<语句>}
/* JavaScript */
function (<参数列表>) {<语句>}
/* Python */
lambda <形参列表> : <函数返回值表达式语句>

虽然以上代码看似简单,但是“万恶”的JavaScript又提供了一个让人欢喜让人狂的功能,如下:
1、JavaScript中闭包可以赋值给变量,此时变量称为函数对象
2、JavaScript能够通过函数形式构造对象
3、函数自调用,形如

(function(<形式参数列表>){<语句>})(实参列表)

4、箭头函数不是函数

前3条都好理解,唯有最后一条。作者的理解是JavaScript引擎将箭头函数直接解析为表达式,函数体内部的上下文对象仍与外部一致。JavaScript中闭包的函数体内部的上下文对象与外部不一致,即JavaScript引擎将闭包解析成一个属于顶级对象的代码段,当条件触发时,由Window对象来调用这个函数,这也是前3条存在的因素。

或许大家能够猜测到,本节所言上下文对象,实际指的就是this这个对象。想要理解this的作用,只需要理解其底层的基础,也就是前2节的内容。


至此,面向对象的底层逻辑就结束了。通用编程语言技术也由面向过程编程过渡到了面向对象编程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值