Content #
可以说,即使经过了八十一难,终于成为了 C 语言绝世高手,我们还是逃不过复杂的堆上对象引用关系导致的 dangling pointer:
你看,在这张图中,当 B 被 free 掉之后,应用程序依然可能会使用指向 B 的指针,这就是比较典型的 dangling pointer 问题,堆上的对象依赖关系可能会非常复杂。所以,我们要正确地写出 free 逻辑,还得先把对象图给画出来。
github.com/pkg/errors 是怎么实现输出错误信息和堆栈信息的呢?
它利用了 fmt 包的一个特性。fmt 包在打印 error 之前会判断当前打印的对象是否实现了 Formatter 接口,这个 formatter 接口只有一个 format 方法。如果要输出的对象实现了这个 Formatter 接口,则调用对象的 Format 方法来打印信息:
type Formatter interface {
Format(f State, c rune)
}
而 github.com/pkg/errors 中提供的各种初始化 error 方法(包括 errors.New)封装了一个 fundamental 结构,这个结构就是实现了 Formatter 接口:
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
这个 fundamental 结构中带着 error 的信息和堆栈信息。并且实现了 Format 方法。在 Format 方法中,判断调用 fmt.Printf 函数的第一个参数,如果是 +v,则打印错误内容和堆栈信息,如果是 v 或者 s,则打印错误内容,如果是 q,则打印转义后的信息:
...同一个负数,其表现形式略有差别。比如十进制数-3,它在8 位运算中是 11111101,即0xFD;在16 位运算中,则是1111111111111101,即0xFFFD。在计算机中,-3 是用0 减去3 得到的,在8 位运算中只能保留结果的低8 位,即 11111101(0xFD);在16 位运算中只能保留结果的低16 位,即 1111111111111101(0xFFFD)。很显然,一个8 位的有符号数,要想用16 位的形式来表示,只需将其最高位,也就是用来辨别符号的那一位(几乎所有的书上都称之为符号位,实际上这并不严谨),扩展到高8 位即可。
处理器专门设计了两条指令来做这件事:cbw(Convert Byte to Word)和 cwd(Convert Word to Double-word)。
cbw 没有操作数,操作码为98。它的功能是,将寄存器AL 中的有符号数扩展到整个AX。如果AL 中的内容为01001111,那么执行该指令后,AX 中的内容为 0000000001001111;如果AL 中的内容为10001101,执行该指令后,AX 中的内容为1111111110001101。
cwd 也没有操作数,操作码为99。它的功能是,将寄存器AX 中的有符号数扩展到DX:AX。
如果AX 中的内容为0100111101111001,那么执行该指令后,DX 中的内容为 0000000000000000,AX 中的内容不变;
如果AX 中的内容为1000110110001011,那么执行该指令后,DX 中的内容为 1111111111111111,AX 中的内容同样不变。
movsb 和 movsw 指令执行时,
正向传送:
反向传送:
不管是正向传送还是反向传送,也不管每次传送的是字节还是字,每传送一次,CX 的内容自动减一。
方向标志DF(Direction Flag):通过将这一位清零或者置1,就能控制movsb 和movsw 的传送方向。方向标志清零指令cld是个无操作数指令,与其相反的是置方向标志指令std。 cld 指令将DF 标志清零,以指示传送是正方向的。 std 指令,它将DF标志置位 1。此时,传送的方向是从高地址到低地址。
所有在个人计算机上使用的显卡,在加电自检之后都会把自己初始化到80×25 的文本模式。在这种模式下,屏幕上可以显示25 行,每行80 个字符,每屏总共 2000 个字符。
0xB8000~0xBFFFF (共320KB)这段物理地址空间就是留给显卡的,用来显示文本。如果显卡出了毛病,计算机就无法通过加电自检过程,计算机是无法启动的,更不要说加载并执行主引导扇区的内容了。

屏幕上的每个字符对应着显存中的两个连续字节,前一个是字符的ASCII 代码,后面是字符的显示属性,包括字符颜色(前景色)和底色(背景色)。

如图所示,字符“H”的ASCII 代码是0x48,其显示属性是0x07;字符“e”的 ASCII 代码是0x65,其显示属性是0x07。
字符的显示属性(1 字节)分为两部分,低4 位定义的是前景色,高4 位定义的是背景色。色彩主要由R、G、B 这3 位决定,可以由红(R)、绿(G)、蓝(B)三原色来配出其他所有颜色。K 是闪烁位,为0 时不闪烁,为1 时闪烁;I 是亮度位,为0时正常亮度,为1 时呈高亮。
下表给出了背景色和前景色的所有可能值。

跨代引用假说(Intergenerational Reference Hypothesis)跨代引用相对于同代引用来说仅占极少数。
依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。
虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。
在与人打交道时,付出者进行真诚性筛选,在与获取者交易时采取互利者的方式,通过这些做法来保护自己,这是完全合理的。但是,在群体当中,还有另外一种做法,可以让付出者确保自己不被人利用。请问是什么方法?
让群体中的每个人都用付出者的方式行事。
这种策略在贾森·盖勒和莉莉安·鲍尔的故事中已经有所预示,他们直接要求接受过他们指导的人,将这种做法传递下去,继续指导更加年轻的同事,以此来作为回报。如果一个群体发展出了一种付出的常规模式,成员就会遵守这种常规模式,做出付出行为,即使他们在其他地方更倾向于做获取者,或是互利者。这减少了付出的风险:当每个人都做出贡献时,蛋糕就更大了,而付出者做出的贡献,也不会总是远远大于他们得到的回报。