计算机系统学习
数组分配和访问
数组的基本原则
数组是某种基本数据类型数据的集合,对于数据类型 T 和整型常数 N,数组的声明如下:
T A[N]
上面的 A 称为数组名称。它有两个效果:
1.它在存储器中分配一个 L*N 字节的连续区域,这里 L 是数据类型 T 的大小(单位为字节)
2.A作为指向数组开头的指针,如果分配的连续区域的起始地址为xa,那么这个指针的值就是xa
指针运算
C语言允许对指针进行运算,而计算出来的值会根据该指针引用的数据类型的大小进行伸缩。也就是说,如果 P 是一个执行类型 T 的数据的指针,P 的值为 xp,那么表达式P+i 的值为 xp+L*i,这里 L 是数据类型T的大小。
数组嵌套
也就是数组的数组,比如二维数组 int A[5][3]。这个时候上面所讲的数组的分配和引用也是成立的。&A[2][0]=xa+24
定长数组和变长数组
如果在编译时数组的长度确定,我们就称为定长数组,反之则称为变长数组。
比如int A[10],就是一个定长数组,它的长度为10,它的长度在编译时已经确定了,因为长度是一个常量。之前的C编译器不允许在声明数组时,将长度定义为一个变量,而只能是常量,不过当前的C/C++编译器已经开始支持动态数组,但是C++的编译器依然不支持方法参数。另外,C语言还提供了类似malloc和calloc这样的函数动态的分配内存空间,我们可以将返回结果强转为想要的数组类型。
异质的数据结构
异质结构是指不同数据类型的数组组合,比如C语言当中的结构(struct)与联合(union)。在理解数组的基础上,这两种数据结构都非常好理解。
结构
考虑一个结构的声明
1 | struct { |
这是一个非常简单的结构体,总共有12个字节,为什么是12个字节呢?这是因为数据对齐的原因。为了提高数据读取的速度,一般情况下会将数据以2的指数倍对齐,具体是2、4、8还是16,得根据具体的硬件设施以及操作系统来决定。
这样做的好处是,处理器可以统一的一次性读取4(也可能是其它数值)个字节,而不再需要针对特殊的数据类型读取做特殊处理。在这个例子来说,也就是说在读取a、b、c时,都可以统一的读取4个字节。特殊的,这里0-3的位置用于存储a,4-7的位置用于存储b,8的位置用于存储c,而9-11则用于填充,其中都是空的。
联合
与结构体不同的是,联合会复用内存空间,以节省内存,考虑一个联合的声明
1 | union { |
这次总共会占4个字节,这是因为a、b、c会共用4个字节,这样做的目的是为了节省内存空间,显然它比结构体节省了8个字节的空间。它与结构体最大的区别就在于,对a、b、c赋值时,联合会覆盖掉之前的赋值,而结构体则不会,结构体可以同时保存a、b、c的值。
在机器级程序中将控制与数据结合起来
指针
指针以一种统一方式,对不同数据结构中的元素产生引用。
GDB调试器的使用
GDB调试器是一种非常实用的辅助工具,可以更好地帮助我们通过阅读代码来推断程序的行为。具体的命令可以参考书上的表格。
内存越界引用和缓冲区溢出
在栈中分配某个字符数组来保存字符串,但是字符串的长度超出了为数组分配的空间,这样就会引起缓冲器溢出。
缓冲区溢出的一个更加致命的使用就是让程序执行它本来不愿意执行的函数。这是一种最常见的通过计算机网络攻击系统安全的方法。
对抗缓冲区溢出攻击
1.栈随机化
为了在系统中插入攻击代码,攻击者既要插入代码,也要插入指向这段代码的指针,这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。
栈随机化的思想使得栈的位置在程序每次运行时都有变化。这类技术称为地址空间布局随机化(Address-Space Layout Randomization),简称ASLR。
通常攻击者使用”空操作雪橇(nop sled)“,使程序”滑过“目标序列,即在实际攻击代码前插入一段很长的nop(读作“no op”,no operation的缩写)指令。
2.栈破坏检测
计算机的第二道防线是能够检测到何时栈已经被破坏。
GCC提供一种栈保护者机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀(canary),也称为哨兵值(guard value),是在程序每次运行时随机产生的。
3. 限制可执行代码区域
最后一招是消除攻击者向系统插入可执行代码的能力。