# 3.8.4 定长数组

C 语言编译器能够优化定长多维数组上的操作代码。这里我们展示优化等级设置为 -O1 时 GCC 采用的一些优化。假设我们用如下方式将数据类型 fix_matrix 声明为 16 × 16 的整型数组:

#define N 16
typedef int fix_matrix[N][N];

(这个例子说明了一个很好的编码习惯。当程序要用一个常数作为数组的维度或者缓冲区的大小时,最好通过 #define 声明将这个常数与一个名字联系起来,然后在后面一直使用这个名字代替常数的数值。这样一来,如果需要修改这个值,只用简单地修改这个 #define 声明就可以了。)

图 3-37a 中的代码计算矩阵 A 和 B 乘积的元素 i,k,即 A 的行 i 和 B 的列 k 的内积。GCC 产生的代码(我们再反汇编成 C),如图 3-37b 中函数 fix_prod_ele_opt 所示。这段代码包含很多聪明的优化。它去掉了整数索引 j,并把所有的数组引用都转换成了指针间接引用,其中包括(1)生成一个指针,命名为 Aptr,指向 A 的行 i 中连续的元素;(2)生成一个指针,命名为 Bptr,指向 B 的列 k 中连续的元素;(3)生成一个指针,命名为 Bend,当需要终止该循环时,它会等于 Bptr 的值。Aptr 的初始值是 A 的行 i 的第一个元素的地址,由 C 表达式 &A[i][0] 给出。Bptr 的初始值是 B 的列 k 的第一个元素的地址,由 C 表达式 &B[0][k] 给出。Bend 的值是假想中 B 的列 j 的第 (n + 1) 个元素的地址,由 C 表达式 &B[N][k] 给出。

下面给出的是 GCC 为函数 fix_prod_ele 生成的这个循环的实际汇编代码。我们看到 4 个寄存器的使用如下:%eax 保存 result%rdi 保存 Aptr%rcx 保存 Bptr,而 %rsi 保存 Bend

/* Compute i,k of fixed matrix product */
int fix_prod_ele(fix_matrix A, fix_matrix B, long i, long k) {
    long j;
    int result = 0;

    for (j = 0; j < N; j++)
        result += A[i][j] * B[j][k];

    return result;
}

a)原始的 C 代码

/* Compute i,k of fixed matrix product */
int fix_prod_ele_opt(fix_matrix A, fix_matrix B, long i, long k) {
    int *Aptr = &A[i][0];      /* Points to elements in row i of A    */
    int *Bptr = &B[0][k];      /* Points to elements in column k of B */
    int *Bend = &B[N][k];      /* Marks stopping point for Bptr       */
    int result = 0;
    do {                       /* No need for initial test             */
        result += *Aptr * *Bptr; /* Add next product to sum            */
        Aptr ++;               /* Move Aptr to next column             */
        Bptr += N;             /* Move Bptr to next row                */
    } while (Bptr != Bend);    /* Test for stopping point              */
    return result;
}

b)优化过的 C 代码

图 3-37 原始的和优化过的代码,该代码计算定长数组的矩阵乘积的元素 i,k。编译器会自动完成这些优化

int fix_prod_ele_opt(fix_matrix A, fix_matrix B, long i, long k)
A in %rdi, B in %rsi, i in %rdx, k in %rcx

1  fix_prod_ele:
2      salq $6, %rdx              Compute 64 * i
3      addq %rdx, %rdi            Compute Aptr = x_A + 64i = &amp;A[i][0]
4      leaq (%rsi,%rcx,4), %rcx   Compute Bptr = x_B + 4k = &amp;B[0][k]
5      leaq 1024(%rcx), %rsi      Compute Bend = x_B + 4k + 1024 = &amp;B[N][k]
6      movl $0, %eax              Set result = 0
7  .L7:                           loop:
8      movl (%rdi), %edx          Read *Aptr
9      imull (%rcx), %edx         Multiply by *Bptr
10     addl %edx, %eax            Add to result
11     addq $4, %rdi              Increment Aptr ++
12     addq $64, %rcx             Increment Bptr += N
13     cmpq %rsi, %rcx            Compare Bptr:Bend
14     jne .L7                    If !=, goto loop
15     rep; ret                   Return

练习题 3.39 利用等式 3.1 来解释图 3-37b 的 C 代码中 AptrBptrBend 的初始值计算(第 3~5 行)是如何正确反映 fix_prod_ele 的汇编代码中它们的计算(第 3~5 行)的。

练习题 3.40 下面的 C 代码将定长数组的对角线上的元素设置为 val

/* Set all diagonal elements to val */
void fix_set_diag(fix_matrix A, int val) {
    long i;
    for (i = 0; i < N; i++)
        A[i][i] = val;
}

当以优化等级 -O1 编译时,GCC 产生如下汇编代码:

void fix_set_diag(fix_matrix A, int val)
A in %rdi, val in %rsi

1  fix_set_diag:
2      movl $0, %eax
3  .L13:
4      movl %esi, (%rdi,%rax)
5      addq $68, %rax
6      cmpq $1088, %rax
7      jne .L13
8      rep; ret

创建一个 C 代码程序 fix_set_diag_opt,它使用类似于这段汇编代码中所使用的优化,风格与图 3-37b 中的代码一致。使用含有参数 N 的表达式,而不是整数常量,使得如果重新定义了 N,你的代码仍能够正确地工作。