《深入理解计算机系统》读书笔记-06
用联合来查看不同数据类型的位模式
具体指的是:根据联合的特性,不同的字段占据相同的存储空间,就可以巧妙地绕开 C 语言本身对数据类型的限制,从而实现对相同位模式的各种数据类型的查看。
这个以前确实没有想到过,因为说实话,联合的使用频率本身也不是很高,相对于其他基本类型和结构体来说,联合跟枚举的存在感都多有不足。
以前也想过怎么样查看某数据在内存中的位模式,同样是被卡在了“一旦进行类型转换,对应数据的位模式就会随之改变”,于是只能通过输出数据的十六进制表示,然后以这个十六进制值另外赋值一个其他类型的变量这种笨重的法子。居然也从来没有想到过还有“联合”这样好用的东西。
数据的对齐机制
之前学习的时候没有学到数据,尤其是结构内部对齐的具体机制,因此只知道结构中的对象“好像”有时候需要补充空字节来使其“完整”,但到底什么时候要补充、在什么的地方补充、怎么个补充法、具体补充几个字节这些问题一概回答不了。
在第 3.9.3 小节“数据对齐”这里,终于看明白了“数据对齐”的为什么和怎么做。
原因
这种机制的初衷是为了简化处理器和内存系统之间硬件接口的设计。使得数据读取的过程与处理器的工作步调相一致,一方面简化操作,一方面也提高了性能。
但在 x86-64 硬件上,这种机制已经不再必要。编译器处于性能考虑,依然保留了这样的机制。
具体细节
对某个具体的基本类型,比如 char、short、int、float、long、double 等等,“对齐要求”,或者说“对齐大小”,就是其本身的字节长度。
此处对
char
类型暂时存疑。因为书上说的对齐原则中的 K 值是 2、4、8 之一,不包括char
的单字节长度
而对于一个结构体而言,稍微就要复杂一些了。具体原则就是其中各个字段中的基本类型对应的偏移量,需要是自身的整数倍,比如一个本身长度为 4 字节的基本类型,其对应的偏移量 x 就应当是 4 的整数倍。其他前后的偏移量都与之无关,仅仅在紧接着前一个基本类型的偏移量不是 4 的整数倍时,添加冗余字节补足需要的偏移量。
对结构体本身而言,这个结构体整体的“对齐要求”(即“对齐大小”)就是指的各字段中,长度最长的基本类型的大小;而这个结构体的绝对地址,也就是首字段的地址,应当是这个“对齐大小”的整数倍。因此,为了满足在构成数组时,结构体的地址依然满足对齐要求,一个结构体的大小也应当是“对齐大小”的整数倍。
举例来说,对于一个字段元素中包含double
类型数据(长度为 8 字节)的结构体而言,其地址应当是 8 的整数倍,其结构体本身的大小也应当是 8 的整数倍,如果最后一个字段的偏移量加上字段大小之后不是 8 的整数倍,也应加上冗余字节进行补足。
缓冲区溢出到底是怎么回事
以前老是听说“缓冲区溢出”,实际上我一直没有搞清楚“缓冲区溢出”到底是怎么回事儿,只知道这样会造成安全隐患,但却并不明白其中的原理。
原来我一直以为“缓冲区溢出”中的“缓冲区”指的是“输入缓冲区”、“输出缓冲区”这样的字符流,我还想,为啥输入缓冲区溢出会导致安全漏洞呢?输入缓冲区溢出就溢出呗,大不了就是丢失信息,在多数情况下也不会造成特别大的危害啊。
看了书之后才恍然大悟,原来所谓“缓冲区溢出”,其中“缓冲区”指的是一般性的、特意分配出来接收某些字节流的存储空间,并不是特指“输入/输出缓冲区”,“溢出”到的位置也是程序的运行时栈。
实际上我们经常看见的数组越界访问,尤其是数组越界写操作,就是一种典型的“缓冲区溢出”。“缓冲区溢出攻击”正是利用了机器级程序无法禁止对缓冲区以外存储空间的读写,从而通过覆写存储空间的特定位置,来执行特定的指令,实现特定的攻击。