2_数组的访问

数组的访问

一个例子:

typedef int zip_dig[5];

zip_dig cmu = {1, 5, 2, 1, 3};
zip_dig mit = {0, 2, 1, 3, 9};
zip_dig ucb = {9, 4, 7, 2, 0};

typedef int zip_dig[5] 表示用 zip_dig 代替 int[5], 即 zip_dig a 等于 int a[5] https://www.runoob.com/cprogramming/c-typedef.html

我们定义了一个数据类型, 它相当于 5 个长度为 int 的这么一个数组的类型, 然后拿它定义了 3 个具体的数组实例

假设我们用寄存器 %edx 来存储数组的起始地址, %eax 表示你所要访问元素的下标

那么对应数组的内存地址就很明确了, 就是你的起始地址加上你的 index * sizeof, 这个和之前的一个寻址模式很类似, 也就是和地址计算模式类似.

所以写出来的指令如下 :

x86-32 下的数组访问代码

; %edx = x
; %eax = dig
movl (%edx, %eax, 4), %eax  ; z[dig]

数组循环示例 (x86-32)

void zincr(zip_dig z)
{
    int i;
    for (i = 0; i < 5; i++)
        z[i]++;
}

我们把刚才那个数组, 长度为 5, 每个数据类型都是 int, 把它作为一个循环, 循环把每一个元素加加

它的循环, 对应出来的汇编代码如下

; edx = z
    movl $0, %eax   ; %eax = i
.L4:
    addl $1, (%edx, %eax, 4)    ; z[i]++
    addl $1, %eax   ; i++
    cmpl $5, %eax   ; i : 5
    jne .L4 ; if !=, goto loop

我们把这个循环代码变形一下, 刚才是用数组下标来访问, 现在我们用指针来访问

void zinvr_p(zip_dig z)
{
    int *zend = z + 5;
    do{
        (*z)++;
        z++; z 为数组 0 号元素的地址, 这个相当于往下一个元素移动的这么个逻辑
    }while (z != zend);
}

转换成更加直观的 C++ 代码:

void zincr_v(zip_dig z)
{
    void *vz = z;
    int i = 0;
    do{
        (*((int *) (vz + i)))++;
        i += ISIZE; /* sizeof(int)*/
    }while (i != ISIZE * 5)
}

汇编代码如下 :

; edx = z = vz
    movl $0, %eax   ; i = 0
.L8:
    addl $1, (%edx, %eax)   ; increment vz + i
    addl $4, %eax   ; i += 4
    cmpl $20, %eax  ; compare i : 20
    jne .L8 ; if !=, goto loop

嵌套数组

就是二维数组, 它是按照行来存储, 第 i 行和第 i + 1 行是连续存储的, 然后每一行有若干个元素, 这若干个元素是连续存储的.

访问嵌套数组中的 "行"

int *get_pgh_zip(int index)
{
    return pgh[index];
}

至于这种二维的或者说嵌套数组, 怎么进行访问, 那么首先就是讲讲怎么访问嵌套数组里面的这一行

比如这个二维数组, 我要访问里面 index 这一行. index 它这个 pgh 的数据类型是 int[5], 即 pgh 每个元素 (里层的数组) 的大小是 5*sizeof(int) = 20

所以行地址, 也就是它的起始地址加上你每一个元素, 实际上这个元素是一个数组, 也就是外层数组的元素, 这个数组的 size 乘上你的 index, index 就是你要访问的那个行的下标, pgh + (20 * index)

相关汇编代码 pgh + 4 * (index + 4 * index)

; %eax = index
    leal (%eax, %eax, 4), %eax  ; 5 * index
    leal pgh(,%eax, 4), %eax    ; pgh + (20 * index)

访问嵌套数组的单个元素 (里层元素)

int *get_pgh_digit(int index)
{
    return pgh[index][dig];
}

pgh[index][dig] 的地址是 : pgh + 20 * index + 4 * dig

相关汇编代码 pgh + 4 * dig + 4 * (index + 4 * index)

还是尽量使用 leal 这种方式来进行地址的计算

; ecx = dig
; eax = index
    leal 0(, %ecx, 4), %edx         ; 4 * dig
    leal (%eax, %eax, 4), %eax      ; 5 * index
    movl pgh(%edx, %eax, 4), %eax   ; *(pgh +4 * dig + 20 * index)

Multi-Level Array 多层数组

我们定义了一个指针数组, 数组长度为 3, 数组每一个元素是一个指针, 长度为 4 字节.

每一个指着又指向了一个 长度为 5 的一个 int 类型数组

zip_dig cmu = {1, 5, 2, 1, 3};
zip_dig mit = {0, 2, 1, 3, 9};
zip_dig ucb = {9, 4, 7, 2, 0};

我们已经有了 3 个一维的数组, 它们地址分别是 16, 36, 56. 注意到这三个数组在内存中是连续存放的, 这个是有可能的.

#define UCOUNT 3
int *univ[UCOUNT] = {mit, cmu, ucb};

然后我们把这三个数组的地址填到 Multi-Level 这个数组里面

访问 Multi-Level Array 中的元素

这个要进行数据元素的计算, 完全和刚才那个嵌套的二维数组不一样, 刚才那个嵌套数组是连续存储的, 这个只是有可能是连续存储的, 它每一行实际上完全没有什么关系

int get_univ_digit(int index, int dig)
{
    return univ[index][dig];
}

现在我们不能像之前嵌套数组那样取地址了, 我们先要把行地址取到, 然后再进行访问内存, 把行地址里面这个元素提取出来

所以它要进行数据运算的话, 用伪代码的形式是这样的 Mem[Mem[univ + 4 * index] + 4 * dig]

; %ecx = index
; %eax = dig
    leal 0(, %ecx, 4), %edx ; 4 * index
    movl univ(%edx), %edx   ; Mem[univ + 4 * index] 首先访问第一维数组, 把指针里面指向的内容取出来, 就是你要访问的那行数据的首地址取出来
    movl (%edx, %eax, 4), %eax  ; Mem[... + 4 * dig] 然后进行第二次访问内存, 取那个实实在在的那个元素