Blog

绝对地址转移与相对近地址转移

Content #

处理器取指令、执行指令是依赖于段寄存器CS 和指令指针寄存器IP 的,8086 处理器取指令时,把CS 的内容左移4 位,加上IP 的内容,形成20 位的物理地址,取得指令,然后执行,同时把IP 的内容加上当前指令的长度,以指向下一条指令的偏移地址。但是,一旦处理器取到的是转移指令,情况就完全变了。

绝对地址转移 #

jmp 指令有多种格式。最典型地,它的操作数可以是直接给出的段地址和偏移地址,这称为绝对地址。比如:

jmp 0x5000:0xf0c0

此时,要转移到的目标位置是非常明确的,即,段地址为0x5000,段内偏移地址为0xf0c0。在这种情况下,指令的操作码为0xEA,故完整的机器指令是:

EA C0 F0 00 50

处理器执行时,发现操作码为0xEA,于是,将指令中给出的段地址传送到段寄存器CS;将偏移地址传送到指令指针寄存器IP,从而转移到目标位置处接着执行。

相对近地址转移 #

jmp 指令使用关键字“near”,表示相对近地址转移,其操作数是以标号(infi)的形式给出。

infi: jmp near infi                 ;无限循环

jmp near infi 的机器指令格式和它的汇编指令格式完全不同,颇具迷惑性。它是一个3 字节指令,操作码是0xE9,后跟一个16 位(两字节)的操作数。编译后的机器码为:

E9 FD FF

需要注意的是,该操作数(FD FF)并非目标位置的偏移地址,而是目标位置相对于当前指令处的偏移量(以字节为单位)。下面就来看偏移量的计算方法:目标位置就是当前指令自己的位置,间隔的长度为0。再用0 减去当前指令长度3,这是一个负数。 0xFFFD 等于十进制数65533,也就是这条指令需要的操作数负3。

编译器如何区分这两种不同的转移方式? #

当它看到JMP 之后是一个绝对地址,如0xF000:0x2000 时,它就知道应当编译成使用操作码0xEA 的直接绝对转移指令。

相反地,如果它发现JMP 之后是一个标号,那么,它就会编译成使用操作码为 0xE9 的相对转移指令。关键字“near”不是最主要的,它仅仅用于指示相对量是16 位的。

Viewpoint #

From #

用xor清零

Content #

如果指令xor dx,dx 中的目的操作数和源操作数相同,那么,不管DX 中的内容是什么,两个相同的数字异或,其结果必定为0,故这相当于将DX 清零。

mov dx, 0 xor dx, dx 尽管上面两条命令都可以用于将寄存器清零,但是编译后,mov dx,0 的机器码是BA 00 00;而xor dx,dx 的机器码则是31 D2,不但较短,而且,因为xor dx,dx 的两个操作数都是通用寄存器,所以执行速度最快。

Viewpoint #

From #

功能选项(functional option)

初始版本 #

  1. 定义option类型。
type option func(*Foo)

其中Foo为需要设置option的对象类型。option是用来设置选项的函数。

  1. 为Foo类型添加应用options列表的函数Option。
func (f *Foo) Option(opts ...option) {
    for _, opt := range opts {
        opt(f)
    }
}
  1. 提供设置特定选项(如verbosity)的函数。
func Verbosity(v int) option {
    return func(f *Foo) {
        f.verbosity = v
    }
}
  1. 客户端设置选项。
foo.Option(pkg.Verbosity(3))

让Option方法返回先前的值 #

用空接口来保存选项值的方案 #

type option func(*Foo) interface{}
func Verbosity(v int) option {
    return func(f *Foo) interface{} {
        previous := f.verbosity
        f.verbosity = v
        return previous
    }
}

func (f *Foo) Option(opts ...option) (previous interface{}) {
    for _, opt := range opts {
        previous = opt(f)
    }
    return previous
}

客户端使用方式如下:

prevVerbosity := foo.Option(pkg.Verbosity(3))
foo.DoSomeDebugging()
foo.Option(pkg.Verbosity(prevVerbosity.(int)))

返回option函数的设置方式 #

空接口显得比较笨重(clumsy),设置选项的返回值是另一个option函数,新返回的option函数在调用时会将值设置为原来的值。

...

什么是 type set

Content #

那什么是 type set(类型集合)呢?伊恩·泰勒给出了这个概念的定义:

  1. 每个类型都有一个 type set;
  2. 非接口类型的类型的 type set 中仅包含其自身。比如非接口类型 T,它的 type set 中唯一的元素就是它自身:{T};
  3. 对于一个普通的、没有 type list 的普通接口类型来说,它的 type set 是一个无限集合。所有实现了这个接口类型所有方法的类型,都是该集合的一个元素,另外,由于该接口类型本身也声明了其所有方法,因此接口类型自身也是其 Type set 的一员;
  4. 空接口类型 interface{}的 type set 中囊括了所有可能的类型。

当类型 T 是接口类型 I 的 type set 的一员时,T 便实现了接口 I;对于使用嵌入接口类型组合而成的接口类型,其 type set 就是其所有的嵌入的接口类型的 type set 的交集。

而对于一个带有自身 Method 的嵌入其他接口类型的接口类型,比如下面代码中的 MyInterface3:

type MyInterface3 interface {
  E1
  E2
  MyMethod03()
}

它的 type set 可以看成 E1、E2 和 E3(type E3 interface { MyMethod03()})的 type set 的交集。

...

升级module的major版本号

升级module的major版本号 #

Go Module 的语义导入版本机制规定:如果同一个包的新旧版本是兼容的,那么它们的包导入路径应该是相同的。反过来说,如果新旧两个包不兼容,那么应该采用不同的导入路径。

而且,我们知道,Go 团队采用了将“major 版本”作为导入路径的一部分的设计。这种设计支持在同一个项目中,导入同一个 repo 下的不同 major 版本的 module,比如:

import (
    "bitbucket.org/bigwhite/m1/pkg1"   // 导入major版本号为v0或v1的module下的pkg1
    pkg1v2 "bitbucket.org/bigwhite/m1/v2/pkg1" // 导入major版本号为v2的module下的pkg1
)

在同一个 repo 下,不同 major 号的 module 就是完全不同的 module,甚至同一 repo 下,不同 major 号的 module 可以相互导入。

这样一来,对于 module 作者 / 维护者而言,升级 major 版本号,也就意味着高版本的代码要与低版本的代码彻底分开维护,通常 Go 社区会采用为新的 major 版本建立新的 major 分支的方式,来将不同 major 版本的代码分离开,这种方案被称为“major branch”的方案。

major branch 方案对于多数 gopher 来说,是一个过渡比较自然的方案,它通过建立 vN 分支并基于 vN 分支打 vN.x.x 的 tag 的方式,做 major 版本的发布。

那么,采用这种方案的 Go Module 作者升级 major 版本号时要怎么操作呢?我们以将 bitbucket.org/bigwhite/m1 的 major 版本号升级到 v2 为例。

...

retract指示符

Content #

从 Go 1.16 版本开始,Go Module 作者还可以在 go.mod 中使用新增加的 retract 指示符,标识出哪些版本是作废的且不推荐使用的。retract 的语法形式如下:

// go.mod
retract v1.0.0           // 作废v1.0.0版本
retract [v1.1.0, v1.2.0] // 作废v1.1.0和v1.2.0两个版本

m1 module 已经演进到v1.0.1版本,某天不小心发布了还处于broken状态的 v1.0.2版本,已经有用户将其拉到proxy缓存中。我们可以将 m1 的 go.mod 更新为如下内容:

//m1的go.mod
module bitbucket.org/bigwhite/m1
go 1.17
retract v1.0.2

然后将 m1 放入 v1.0.3 标签中并发布。现在 m1 的消费者 c2 要查看 m1 是否有最新版本时,可以查看到以下内容(c2 本地环境使用 go1.17 版本):

$GONOPROXY=bitbucket.org/bigwhite/m1 go list -m -u all
... ...
bitbucket.org/bigwhite/m1 v1.0.2 (retracted) [v1.0.3]

从 go list 的输出结果中,我们看到了 v1.0.2 版本上有了 retracted 的提示,提示这个版本已经被 m1 的作者作废了,不应该再使用,应升级为 v1.0.3。

...

存储能力与提取能力是不对等的

Content #

根据遗忘式学习理论,存储能力与提取能力是不对等的,具体体现在哪里?

存储能力只会越变越强,且有足够的空间容纳人一生中每一秒的所见所闻。而我们只能提取大脑发出的提示与给定的线索有关的记忆,而且仅仅只是非常有限的一小部分。

Viewpoint #

From #

半正则(semiregular)的对象

Content #

C++中所谓的半正则(semiregular)的对象指的是什么?

一个用来返回的对象,通常应当是可移动构造 / 赋值的,一般也同时是可拷贝构造 / 赋值的。如果这样一个对象同时又可以默认构造,我们就称其为一个半正则(semiregular)的对象。

class matrix {
public:
  // 普通构造
  matrix(size_t rows, size_t cols);
  // 半正则要求的构造
  matrix();
  matrix(const matrix&);
  matrix(matrix&&);
  // 半正则要求的赋值
  matrix& operator=(const matrix&);
  matrix& operator=(matrix&&);
};

Viewpoint #

From #

付出者如何对待他人的潜力

Content #

付出者是如何对待他人的潜力的?

付出者不会等待潜力发出信号。因为他们倾向于信任他人,对于他人的意图保持乐观态度,所以作为领导者、管理者和导师,付出者会从每个人身上看到潜力。在默认状态下,付出者会自动将每个人视为高潜力者,这就是C.J.斯肯德能够培养出那么多明星学生的原因。他的过人之处并不是善于发现天才,他只是一开始就将每个人视为天才,并努力帮助他们做到最好。在斯肯德眼中,每个走进教室的学生都是一块璞玉——有能力,而且愿意被发掘、被切割和被打磨。他能在别人看不到的地方发现潜能,这带来了一系列的自我实现预言。

Viewpoint #

From #

伪指令

Content #

要放在程序中的数据是用DB 指令来声明(Declare)的,DB 的意思是声明字节(Declare Byte),所以,跟在它后面的操作数都占一个字节的长度(位置)。注意,如果要声明超过一个以上的数据,各个操作数之间必须以逗号隔开。

除此之外,DW(Declare Word)用于声明字数据,DD(Declare Double Word)用于声明双字(两个字)数据,DQ(Declare Quad Word)用于声明四字数据。

DB、DW、DD 和DQ 并不是处理器指令,它只是编译器提供的汇编指令,所以称做伪指令(pseudo Instruction)。

伪指令是汇编指令的一种,它没有对应的机器指令,所以它不是机器指令的助记符,仅仅在编译阶段由编译器执行,编译成功后,伪指令就消失了,所以在程序执行时,伪指令是得不到处理器光顾的,实际上,程序执行时,伪指令已不存在。

Viewpoint #

From #