CMU 15-213 Intro to Computer Systems Lecture 8
课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html
课程资料:https://github.com/EugeneLiu/translationCSAPP
课程视频:https://www.bilibili.com/video/av31289365/
这一讲介绍了机器级编程4:数据。
数组
- 基本原则
- T A [L];
- 上述声明表示数据类型为T,长度为L的数组
- 内存中是L*sizeof(T)个字节的连续分配区域
数组读取的例子
#define ZLEN 5
typedef int zip_dig[ZLEN];
zip_dig cmu = { 1, 5, 2, 1, 3 };
zip_dig mit = { 0, 2, 1, 3, 9 };
zip_dig ucb = { 9, 4, 7, 2, 0 };
int get_digit(zip_dig z, int digit)
{
return z[digit];
}
IA32
# %rdi = z
# %rsi = digit
movl (%rdi,%rsi,4), %eax # z[digit]
- 寄存器%rdi包含数组的起始地址
- 寄存器%rsi包含数组索引
- 所需数字位于%rdi + 4*%rsi
- 使用内存引用(%rdi,%rsi,4)
数组循环例子
void zincr(zip_dig z) {
size_t i;
for (i = 0; i < ZLEN; i++)
z[i]++;
}
反汇编的结果为
# %rdi = z
movl $0, %eax # i = 0
jmp .L3 # goto middle
.L4: # loop:
addl $1, (%rdi,%rax,4) # z[i]++
addq $1, %rax # i++
.L3: # middle
cmpq $4, %rax # i:4
jbe .L4 # if <=, goto loop
rep; ret
多维(嵌套)数组
- 声明
- T A [R] [C];
- 数据类型为T的2D数组
- R行,C列
- 类型T元素需要K个字节
- 数组大小
- $\text{R} \times \text{C} \times \text{K}$字节
- 内存安排
- 按行排列
嵌套数组例子
#define PCOUNT 4
zip_dig pgh[PCOUNT] =
{{1, 5, 2, 0, 6},
{1, 5, 2, 1, 3 },
{1, 5, 2, 1, 7 },
{1, 5, 2, 2, 1 }};
内存中的结果
- “zip_dig pgh [4]”等价于“int pgh [4] [5]”
- 变量pgh:4个元素的数组,内存中连续分配
- 每个元素都是5个int的数组,内存中连续分配
- 内存中所有元素都是按行排列
嵌套数组读取
嵌套数组行的读取
- 行向量
- A[i]是C个元素的数组
- 类型T的每个元素需要K个字节
- 起始地址为$\text{A} + \text{i} \times(\text{C} \times \text{K})$
例如$\text{int A[R][C]}$
考虑如下代码
int *get_pgh_zip(int index)
{
return pgh[index];
}
# %rdi = index
leaq (%rdi,%rdi,4),%rax # 5 * index
leaq pgh(,%rax,4),%rax # pgh + (20 * index)
- 行向量
- pgh[index]是5个整数的数组
- 起始地址$\text{pgh + 20} \times \text{index}$
- 机器码
- 计算并返回地址
- 计算方式为$\text{pgh} + 4 \times (\text{index} + 4 \times \text{index})$
嵌套数组元素访问
- 数组元素
- A[i][j]是类型T的元素,需要K个字节
- 地址为$\text{A + i} \times \text{(C} \times \text{K)} + \text{j} \times \text{K} = \text{A} + (\text{i} \times \text{C} + \text{j})\times \text{K}$
例如$\text{int A[R][C]}$
考虑如下代码
int get_pgh_digit
(int index, int dig)
{
return pgh[index][dig];
}
leaq (%rdi,%rdi,4), %rax # 5*index
addl %rax, %rsi # 5*index+dig
movl pgh(,%rsi,4), %eax # M[pgh + 4*(5*index+dig)]
- 数组元素
- pgh[index] [dig]是int
- 地址:$\text{pgh + 20} \times \text{index + 4} \times \text{dig = pgh + 4} \times \text{(5} \times\text{index + dig)}$
多级数组例子
zip_dig cmu = { 1, 5, 2, 1, 3 };
zip_dig mit = { 0, 2, 1, 3, 9 };
zip_dig ucb = { 9, 4, 7, 2, 0 };
#define UCOUNT 3
int *univ[UCOUNT] = {mit, cmu, ucb};
- 变量univ表示由3个元素组成的数组
- 每个元素都是一个指针
- 8字节
- 每个指针都指向int数组
多级数组读取
int get_univ_digit
(size_t index, size_t digit)
{
return univ[index][digit];
}
salq $2, %rsi # 4*digit
addq univ(,%rdi,8), %rsi # p = univ[index] + 4*digit
movl (%rsi), %eax # return *p
ret
- 计算方式
- 元素访问$\text{Mem[Mem[univ + 8} \times \text{index] + 4} \times \text{digit]}$
- 必须做两次内存读取
- 首先获取指向行数组的指针
- 然后访问数组中的元素
还有一些例子这里从略。
结构
结构表示
struct rec {
int a[4];
size_t i;
struct rec *next;
};
- 结构表示为内存块
- 足够容纳所有的字段
- 根据声明的顺序安排字段
- 即使另一个排序可以产生更紧凑的表示
- 编译器确定整体大小+字段位置
- 机器级程序不了解源代码中的结构
结构和对齐
struct S1 {
char c;
int i[2];
double v;
} *p;
- 没有对齐的数据
- 对齐的数据
- 原始数据类型需要K个字节
- 地址必须是K的倍数
对齐原则
- 对齐数据
- 原始数据类型需要K个字节
- 地址必须是K的倍数
- 在某些机器上是必需的; x86-64上被建议使用
- 对齐数据的动机
- 通过(对齐的)4或8个字节的块访问内存(取决于系统)
- 加载或存储跨越四字边界的数据效率低下
- 当数据跨越2页时,虚拟内存会更复杂
- 编译器
- 在结构中插入间隙以确保正确对齐字段
对齐的特定情况(x86-64)
- 1个字节:char,…
- 地址无限制
- 2个字节:short,…
- 地址的最低1位必须为$0_2$
- 4个字节:int,float等
- 地址的最低2位必须为$00_2$
- 8个字节:double,long,char *,…
- 地址的最低3位必须为$000_2$
- 16个字节:long double(Linux上的GCC)
- 地址的最低4位必须为$0000_2$
节省空间
将较大的数据放在前面
struct S4 { char c; int i; char d; } *p;
struct S5 { int i; char c; char d; } *p;
有效位数($K=4$)
浮点数
背景
- 历史
- x87 FP
- 旧版,非常难看
- SEE FP
- Shark machines(CMU的虚拟机)支持
- 特殊情况使用矢量指令
- AVX FP
- 最新版本
- 类似于SEE FP
- x87 FP
SSE3
XMM寄存器
- 总共16个,每个16个字节
基本操作
FP基础
- 参数传入%xmm0,%xmm1 …
- 结果以%xmm0返回
- 所有XMM寄存器均已保存caller
例子
float fadd(float x, float y)
{
return x + y;
}
# x in %xmm0, y in %xmm1
addss %xmm1, %xmm0
ret
double dadd(double x, double y)
{
return x + y;
}
# x in %xmm0, y in %xmm1
addsd %xmm1, %xmm0
ret
FP内存引用
- 在常规寄存器中传递的整数(和指针)参数
- XMM寄存器中传递的FP值
- 在XMM寄存器之间以及在存储器和XMM寄存器之间使用不同的mov指令
double dincr(double *p, double v)
{
double x = *p;
*p = x + v;
return x;
}
# p in %rdi, v in %xmm0
movapd %xmm0, %xmm1 # Copy v
movsd (%rdi), %xmm0 # x = *p
addsd %xmm0, %xmm1 # t = x + v
movsd %xmm1, (%rdi) # *p = t
ret
总结
- 数组
- 元素打包到内存的连续区域
- 使用索引计算来定位单个元素
- 结构体
- 元素打包到单个内存区域
- 使用编译器确定的偏移量进行访问
- 可能需要内部和外部填充以确保对齐
- 组合方式
- 可以任意嵌套结构和数组代码
- 浮点
- 在XMM寄存器中保存和操作的数据
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Doraemonzzz!
评论
ValineLivere