为什么数组从索引0开始:内存层面的解释
Source: Dev.to
📌 目录
连续内存块的数组
在 C/C++ 中,数组本质上是一个 固定大小、元素类型相同的集合,存储在 连续的内存位置。当你声明
int arr[100];
编译器会为 100 个连续的整数 分配空间。
在大多数现代系统上:
- 一个
int通常占用 4 字节(在 32‑ 或 64‑ 位架构上)。 - 因此整个数组占用 400 字节,在内存中紧密排列。
Source: …
arr[i] 的工作原理:指针算术解释
数组从索引 0 开始的真正原因与计数或约定无关。它来源于语言 定义 数组下标的方式。
当你写 arr[i] 时,它会 直接翻译 成 *(arr + i)。
这不是实现细节;它是 C/C++ 语言定义的一部分。
拆解 *(arr + i)
| 部分 | 含义 |
|---|---|
arr | 数组的 基地址(即 &arr[0])。 |
+ i | 指针算术 —— 加上 i × sizeof(元素类型) 字节,而不是 i 字节。 |
* | 对计算得到的地址解引用,以读取或写入该值。 |
所以 arr[i] 的字面意思是:从数组起始位置向后移动 i 个元素,然后访问存放在那里的值。
示例
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40};
// 直接数组访问
printf("arr[1]: %d\n", arr[1]); // 输出: 20
// 等价的指针写法
printf("*(arr + 1): %d\n", *(arr + 1)); // 同样输出: 20
return 0;
}
两个语句都打印 20,因为它们实际上是完全相同的操作。
为什么这会导致索引从 0 开始
第一个元素 并不是“相隔一步”——它位于 基地址 本身。
- 与基地址的距离 = 0
- 偏移量 = 0
- 索引 = 0
因此,第一个元素的访问方式是:
arr[0] == *(arr + 0) // no adjustment needed
每个后续元素通过在内存中向前移动来访问:
arr[1] == *(arr + 1) // skip 1 element (4 bytes for int)
arr[2] == *(arr + 2) // skip 2 elements (8 bytes)
索引仅仅是以 元素为单位的偏移量。偏移量从 0 开始,因为没有任何东西可以比原点的距离更近。这直接源于内存寻址和指针算术的工作方式。
如果数组从索引 1 开始?
假设数组是 基于 1 的索引,像 MATLAB 那样,首个元素将通过 arr[1] 访问。
指针运算本身 不会 改变;arr[i] 仍然会被翻译为 *(arr + i)。直接套用这条规则会得到:
arr[1] → *(arr + 1) // 指向 *第二* 个元素,而不是第一个
为了让基于 1 的索引工作,编译器需要把每一次访问重写为:
arr[i] → *(arr + (i - 1))
额外的减法会导致 语义不匹配,与硬件自然的 “基址 + 偏移” 寻址模型相冲突,增加了边界推理的复杂度,并且模糊了 “相对于基址的偏移” 这一简单的思维模型。虽然现代编译器可以将减法优化掉,但这一步概念上的额外处理是没有必要的。
Source: …
为什么 arr[i] 和 i[arr] 意思相同
C 标准将下标运算符定义为:
a[b] == *(a + b)
因为 加法是交换律的(a + b == b + a),我们同样有:
*(a + b) == *(b + a)
于是:
a[b] == b[a]
这 不是技巧 或未定义行为;它是语言定义的直接结果。
演示
#include <stdio.h>
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
// 普通的数组下标
printf("arr[3] = %d\n", arr[3]); // 输出: 4
// 等价但不常见的下标写法
printf("3[arr] = %d\n", 3[arr]); // 输出: 4
return 0;
}
两个语句都会打印 4,因为 arr[3] 和 3[arr] 计算得到的是同一个地址。
结论
- 零基索引 与 C/C++ 定义数组下标的方式(
*(base + offset))完全一致。 - 第一个元素位于 基地址,因此它的偏移量是 0。
- 使用一基索引则需要在每次访问时额外进行
-1的调整,增加不必要的复杂性。 - 定义
a[b] == *(a + b)也解释了为什么arr[i]与i[arr]可以互换使用。
理解这些底层细节可以阐明,看似奇怪的数组索引从 0 开始实际上是对语言和底层硬件最自然、最高效的选择。
#include <stdio.h>
int main(void) {
int arr[] = {1, 2, 3, 4};
int i = 3;
printf("%d\n", i[arr]); // Output: 4
return 0;
}
注意: 虽然 i[arr] 在 C 中是合法的,但在实际代码中很少使用,因为它会降低可读性。它之所以存在,仅仅是因为数组下标是用指针算术来定义的。
结论
在 C/C++ 中,数组索引并不是在计数位置,而是测量相对于基地址的偏移量。