矢量:按位置索引

矢量:按位置索引

向量就是一个数组:在内存中彼此相邻存储的一串数字。在上一课中,你通过为每个元素将指针向前移动 4 个字节来求和。同样的方法在这里也适用。还有一种常见的访问元素的方式:通过索引。索引是从开头数起的元素位置,第一个元素的索引为 0,第二个为 1,依此类推。

通过移动指针求向量之和

通过前进指针来求和向量
.data
a: .word 5, 10, 15, 20 # 四个元素
.text
la t0, a # t0 = a 的地址
li t1, 4 # t1 = 剩余元素个数
li t2, 0 # t2 = 累计总和
loop:
beq t1, zero, done
lw t3, 0(t0) # 加载当前元素
add t2, t2, t3 # 加到总和中
addi t0, t0, 4 # 前进到下一个字 (4 字节)
addi t1, t1, -1 # 剩余数减一
j loop
done:
mv a0, t2 # 打印总和:5+10+15+20 = 50
li a7, 1
ecall
li a7, 10
ecall

循环的运行如下。t0 保存 a 的地址,t1 保存剩余元素的数量,从 4 开始。每一轮中, 在计数变为零时结束循环。 加载 t0 中地址处的元素。 将该值加到累计总和中。 将指针推进到下一个字。 减少计数。经过四轮(5 + 10 + 15 + 20)后,done 打印 50。这与内存课中使用的模式相同,现在应用于向量。

从索引到地址

索引计算根据基址指针和位置计算地址:address = base + index * 4。其中的 4 是一个字的大小(以字节为单位)。编译器会为 C 等语言中的 a[i] 生成同样的计算。索引本身从不访问内存。只有由它计算出的地址才会访问内存。

使用移位对地址进行索引
slli t5, t3, 2 # t5 = 索引 * 4 (字的大小)
add t6, t0, t5 # t6 = element[index] 的地址
lw t6, 0(t6) # t6 = element[index]

将 t3 中的索引左移 2 位,即将其乘以 4(两次翻倍:2 x 2),也就是一个字的大小。将结果加到基址指针上就得到 element[index] 的地址。这个移位与算术课中的二的幂方法相同,也是按字的大小缩放索引的标准方式。

地址计算
address=base+0×4=base+0
a[0]3
0x10010000
a[1]6
0x10010004
a[2]9
0x10010008
a[3]12
0x1001000c

a[0]:索引 0,偏移 0。

当索引在 0、1、2、3 之间循环时,指针条和十六进制徽章一起移动。它们以两种形式呈现同一事实:i 作为位置,以及该位置在 将其缩放 4 倍并加到基址后产生的地址。十六进制数字是模拟器运行此程序时 a 实际占用的地址。目标是预测下一个地址,在它出现之前。这样你就能读懂递增循环中的 ,或类 C 语言中的 a[i],并想象它触及的内存地址,而不是把这种表示法当作黑箱。

按索引求向量之和

使用索引求和向量
.data
a: .word 5, 10, 15, 20 # 四个元素
.text
la t0, a # t0 = a 的基地址
li t1, 4 # t1 = 元素个数
li t2, 0 # t2 = 索引,起始于 0
li t3, 0 # t3 = 累计总数
loop:
beq t2, t1, done
slli t4, t2, 2 # t4 = 索引 * 4 (字节偏移)
add t5, t0, t4 # t5 = a[index] 的地址
lw t5, 0(t5) # t5 = a[index]
add t3, t3, t5 # total += a[index]
addi t2, t2, 1 # 前进到下一个索引
j loop
done:
mv a0, t3 # 打印总和:5+10+15+20 = 50
li a7, 1
ecall
li a7, 10
ecall

循环的运行如下。t2 是索引,从 0 开始。每一轮中, 在索引达到 4(即 t1 中保存的元素数量)时结束循环。 将索引转换为字节偏移量。 将该偏移量加到 t0 中的基址上,产生 a[index] 的地址。 加载存储在那里的值。该值被加到 t3 中的累计总和中, 前进到下一个索引。经过四轮(5 + 10 + 15 + 20)后,done 打印 50。

逐元素相加两个向量

c[i] = a[i] + b[i]
.data
a: .word 3, 6, 9, 12 # 第一个向量
b: .word 1, 2, 3, 4 # 第二个向量
c: .space 16 # 为结果留出的 4 个字空间
.text
la t0, a # a 的基址
la t1, b # b 的基址
la t2, c # c 的基址
li t3, 0 # 索引,从 0 开始
li t4, 4 # 元素个数
li s0, 0 # 累计总数
loop:
beq t3, t4, done
slli t5, t3, 2 # 字节偏移量 = 索引 * 4
add t6, t0, t5 # a[index] 的地址
lw t6, 0(t6) # a[index] 的值
add a0, t1, t5 # b[index] 的地址
lw a0, 0(a0) # b[index] 的值
add t6, t6, a0 # a[index] + b[index]
add a0, t2, t5 # c[index] 的地址
sw t6, 0(a0) # c[index] = a[index] + b[index]
add s0, s0, t6 # 总和 += c[index]
addi t3, t3, 1 # 前进到下一个索引
j loop
done:
mv a0, s0 # 打印总和:4+8+12+16 = 40
li a7, 1
ecall
li a7, 10
ecall

循环的运行如下。 将索引 t3 转换为字节偏移量。将其加到每个基址指针(t0、t1、t2)上,就得到 a[i]、b[i] 和 c[i] 的地址。每一轮中,两个元素都被加载、相加、存入 c,并加到 s0 中的累计总和中。 前进到下一个索引,循环重复,直到所有四个元素都被处理完。

💡
这种索引模式,base + index * element size,是任何语言中每次数组访问的编译方式。记住它:下一课将同时两次使用同一思路来访问一个矩阵。