3.17 数组
数组在内存中连续分配,编译器通过地址计算实现数组元素的访问。
一维数组
内存布局
一维数组的元素在内存中连续存放,占用空间为 N × sizeof(T) 字节。
// C 代码
char A[4]; // 4 × 1 = 4 字节
short B[4]; // 4 × 2 = 8 字节
int C[4]; // 4 × 4 = 16 字节
long D[4]; // 4 × 8 = 32 字节内存布局(以 long D[4] 为例):
地址 内容
D D[0] ← 低地址
D+8 D[1]
D+16 D[2]
D+24 D[3] ← 高地址
元素访问
// C 代码
long array[N];
long get_array(long *arr, long index) {
return arr[index];
}# 汇编实现
get_array:
# %rdi = arr, %rsi = index
movq (%rdi, %rsi, 8), %rax # rax = arr[index]
ret地址计算:arr + index × 8
通用公式
对于类型 T 的数组 T A[N],访问 A[i] 的地址为:
&A[i] = A + i × sizeof(T)
多维数组
行优先存储
C 语言使用行优先(row-major)顺序存储多维数组:
// C 代码
long A[M][N]; // M 行 N 列内存布局:A[0][0] A[0][1] ... A[0][N-1] A[1][0] A[1][1] ... A[M-1][N-1]
├────── 第 0 行 ──────┤ ├────── 第 1 行 ──────┤
二维数组元素访问
// C 代码
long get_element(long A[M][N], long i, long j) {
return A[i][j];
}# 汇编实现
get_element:
# %rdi = A, %rsi = i, %rdx = j
# 计算 A + i × N × 8 + j × 8
imulq $N, %rsi, %rsi # rsi = i × N
addq %rsi, %rdi # rdi = A + i × N × 8
movq (%rdi, %rdx, 8), %rax # rax = A[i][j]
ret地址公式:&A[i][j] = A + i × (N × sizeof(T)) + j × sizeof(T)
多维数组的行指针
// C 代码
long get_row(long A[M][N], long i) {
return (long)&A[i];
}get_row:
# %rdi = A, %rsi = i
leaq (%rdi, %rsi, N*8), %rax # rax = A + i × N × 8
ret元素大小与步长
元素的大小决定了数组访问的步长(stride):
| 类型 | 大小 | 元素 i 的地址 | 步长 |
|---|---|---|---|
char | 1 | arr + i | 1 |
short | 2 | arr + i × 2 | 2 |
int | 4 | arr + i × 4 | 4 |
long | 8 | arr + i × 8 | 8 |
double | 8 | arr + i × 8 | 8 |
int* | 8 | arr + i × 8 | 8 |
固定大小数组 vs 指针
// 固定大小数组
long A1[4]; // 栈上分配 32 字节
// A1 的类型是 long[4],不是指针
// 指针
long *A2; // 栈上分配 8 字节(指针)
A2 = malloc(32); // 堆上分配
// 参数声明——实际是指针
void func(long A[4]) { // 等价于 long *A
// sizeof(A) == 8,不是 32!
}关键区别:
sizeof(A1)= 32(整个数组)sizeof(A2)= 8(指针)- 函数参数中的数组声明退化为指针
数组的循环处理
// C 代码:数组求和
long array_sum(long *arr, long n) {
long sum = 0;
for (long i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}# 汇编实现(Guarded-Do 模式)
array_sum:
movq $0, %rax # sum = 0
movq $0, %rcx # i = 0
cmpq %rsi, %rcx # 比较 i 和 n
jge .Ldone # if (i >= n) done
.Lloop:
addq (%rdi, %rcx, 8), %rax # sum += arr[i]
addq $1, %rcx # i++
cmpq %rsi, %rcx # 比较 i 和 n
jl .Lloop # if (i < n) continue
.Ldone:
ret指针版本优化
编译器常将数组索引转化为指针操作:
# 优化版本(使用指针而非索引)
array_sum_opt:
movq $0, %rax # sum = 0
leaq (%rdi, %rsi, 8), %rcx # end = arr + n
.Lloop:
cmpq %rcx, %rdi # 比较 arr 和 end
jae .Ldone # if (arr >= end) done
addq (%rdi), %rax # sum += *arr
addq $8, %rdi # arr++
jmp .Lloop
.Ldone:
ret变量长度数组(VLA)
C99 支持变量长度数组,数组维度在运行时确定。
// C 代码:VLA 作为函数参数
long sum_vla(long n, long A[n][n]) {
long sum = 0;
for (long i = 0; i < n; i++) {
for (long j = 0; j < n; j++) {
sum += A[i][j];
}
}
return sum;
}VLA 的地址计算需要在运行时动态计算行步长:&A[i][j] = A + i × n × 8 + j × 8。
数组的栈分配
void stack_array(long n) {
long local_arr[4]; // 固定大小,编译时分配
long vla_arr[n]; // VLA:运行时分配
}stack_array:
pushq %rbp
movq %rsp, %rbp
# 固定大小数组:subq $32, %rsp (4 × 8 = 32)
# VLA:动态计算大小后分配
leaq (, %rdi, 8), %rax # rax = n * 8
subq %rax, %rsp # 动态分配 VLA 空间
# ...
movq %rbp, %rsp
popq %rbp
ret指针数组 vs 多维数组
// 真正的多维数组(连续内存)
long A[3][4]; // 固定每行 4 列,连续 96 字节
// 指针数组(每行可独立分配)
long *B[3]; // 3 个指针
B[0] = malloc(4 * 8); // 每行可不同长度
B[1] = malloc(8 * 8);
B[2] = malloc(2 * 8);
// 访问差异
long x = A[1][2]; // *(A + 1*4*8 + 2*8)
long y = B[1][2]; // *(*(B + 1*8) + 2*8) 两次访存相关笔记
-
- 寻址模式在数组访问中的应用
-
- 数组地址的计算
-
- 结构体数组的内存布局
-
- 数组元素的对齐要求
链接到
- 上一个知识点:3.16 调用者与被调用者保存寄存器
- 下一个知识点:3.18 结构体与联合