为什么 C 语言数组是从 0 开始计数的?

C 语言等大多数编程语言的数组从 0 开始而不从 1 开始,有两个原因:

第一:地址计算更方便

C 语言从 0 开始的话,array[i] 的地址就正好是:

(array + i) 

如果是从 1 开始的话,就是

(array + i - 1) 

多一次计算,性能受影响,再扩展到二维数组的话 array[i][j] 从 0 开始的地址是:

(array + i * N + j) 

多整洁,而从 1 开始要变成

(array + (i - 1) * N + (j - 1)) 

更繁琐。并且用 1 开始的话,同一个地址用 “指针+偏移”寻址和用 “数组+下标” 寻址还不能统一,经常要换算,何必呢?

第二:计算机硬件系统就是从 0 开始寻址的

物理内存地址寻址,端口寻址都是从 0 开始的,比如 32 位电脑的内存,地址范围就是:

[0, 2 ^ 32 - 1]

刚好用一个 32 位整数就能表达,而如果内存从 1 开始寻址,那么 32 位电脑的地址范围就会变成:

[1, 2 ^ 32]

那么最高地址 2 ^ 32 就需要一个 33 位的整数才能表达了,纯粹浪费资源。

其他的端口地址,DMA 通道等也都遵从这个从 0 开始的原则,那么用 3 比特表示 DMA 通道的话,更好可以表达 8 个通道 (0 - 7),而从 1 开始的话,同样 3 比特就只能表达 7 个通道了(1 - 7),一样是在浪费资源。

所以贴近系统的语言自然选择遵从硬件设定,除了第一条说的寻址计算更简单外,也能和计算机系统保持一致性,同时还能统一指针寻址和数组寻址的用户体验。

Dijkstra 解释过编程语言这么做的原因只是遵从硬件设计:

The decision taken by the language specification & compiler-designers is based on the decision made by computer system-designers to start count at 0.

所以 C 语言数组从零开始,目的在于:1)性能更好;2)统一数组和指针寻址;3)遵从硬件寻址法。

除此之外还有一些理论上的原因。

第三:数学上的原因

除去数组索引外,Dijkstra 主张一切计数应该从 0 开始,并且写了一篇文章解释:

(点击 more/continue 继续)

Dijkstra: Why numbering should start at zero

他明确的批过 Fortran 和 Pascal 等从 1 开始的早期语言考虑不周:

他给出了无懈可击的理由,大概论点是,对于 2,3,4,…,12 的整数序列,有几种表述:

a)2 <= i < 13
b)1 < i <= 12
c)2 <= i <= 12
d)1 < i < 13

然后说明:

  • 对于左边 a <= x 比 a < x 的表述法更好,因为如果用 a < x 表示一个序列,你总要提供一个比第一个元素小一号的数字,不但恶心,往往不可能(存在最小的有理数,不存在最大有理数),所以 a <= x 的表述更好;
  • 对于右边 x < b 的表述比 x <= b 的表述更好,因为当 a = b 时,a <= x < b 可以表示一个空集,而 a <= x <= b 无法表示一个空集
  • 方案 a) 和方案 b) 可以一眼看出序列的长度。
  • 方案 a) 和方案 d) 更容易表述邻接的序列。

如此证明左闭右开的方案 a) a <= x < b 更适合表述表述一个序列。

Dijkstra 论证完 a <= x < b 更是更好的选择后,给出结论,长度为 N 的数组从 0 开始更好,因为 0 <= x < N 的表述比 1 <= x < N+1 更清晰。

扩展阅读:

别被忽悠了 Lua 数组真的也可以从 0 开始索引?

<think>嗯,用户问的是C/C++数组索引从0开始的设计原因和底层优势。这个问题其实很有意思,因为大多数初学者都会困惑为什么索引不从更直观的1开始。 从用户引用的资料来看,ta应该已经接触过一些相关资料,比如引用[1]提到算法和数据结构统一性的优势,引用[4]则强调了随机访问的特性。用户可能正在学习底层原理,或者遇到了相关面试题。 我需要从多个维度解释这个问题。首先硬件层面,CPU的地址计算是核心。假设有个数组起始地址是1000,每个元素4字节。那么: - 索引0对应1000 + 0*4 - 索引1对应1000 + 1*4 这种计算方式最符合硬件加法器的设计,不需要额外减法操作。如果用1起始,每次都要计算(地址 + (i-1)*size),多出指令周期。 编译器优化也很关键。引用[3]提到的性能优化视角在这里很适用——从0开始能让循环变量的比较指令更简单。比如遍历n个元素时,i < n 比 i <= n 少一次比较操作。 用户可能没意识到的是历史传承因素。C语言继承自B语言,而B语言又受BCPL影响,这些早期语言都采用0起始索引。这种设计被证明更符合计算机思维而非人类思维。 最后要强调类型系统的统一性。在C中,a[i] 等价于 *(a+i),如果从1开始就会破坏这种优雅对称。这点在引用[1]提到“语言设计简洁性”时其实已经暗示了。 用户似乎对底层很感兴趣,所以回答时要多举具体内存地址的例子。比如展示: arr[0] -> 0x1000 arr[1] -> 0x1004 这样ta能直观看到“偏移0”的自然性。 对了,还得提提指针运算的陷阱。用户之前问过指针偏移的问题,说明ta在实际编码中可能踩过坑。可以自然带出:如果强行用1起始索引,写循环时容易产生off-by-one错误。 最后生成的问题要延续ta的学习路径,比如内存对齐对数组访问的影响,或者对比其他语言(Matlab/Fortran)的1起始设计。这样能帮ta建立体系化认知。</think>C/C++ 数组索引从 0 开始的设计是计算机科学中的基础决策,其底层优势主要体现在以下方面: --- ### 一、核心设计原因 1. **与内存地址的直接映射** 数组名 `a` 是首元素的内存地址,元素 `a[i]` 的地址计算公式为: $$ \text{address}(a[i]) = \text{base\_address} + i \times \text{element\_size} $$ 索引 0 对应零偏移量(`base_address + 0`),消除了减法运算的需求[^4]。 2. **硬件指令优化** CPU 的寻址指令天然支持“基地址 + 偏移量”模式。例如 x86 汇编: ```assembly mov eax, [ebx + ecx*4] ; 直接计算 a[i] 地址 (ecx=i) ``` 若索引从 1 开始,需额外指令计算 `i-1`,降低效率[^1]。 --- ### 二、底层优势 | **优势** | **说明** | **示例** | |-------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------| | **减少指令周期** | 省去索引减法操作 (`i-1`),提升访问速度 | 循环 `for(i=0; i<n; i++)` 比 `for(i=1; i<=n; i++)` 少一次减法指令 | | **统一指针与数组语法** | 指针运算 `*(a+i)` 等价于数组访问 `a[i]` | `a[0]` 即 `*a`,符合“零偏移”直觉 | | **循环边界优化** | 比较条件 `i < n` 比 `i <= n` 更高效(少一次比较) | 编译器生成更简洁的机器码:<br> `cmp i, n` → `jl loop` (无需判断相等) | | **连续内存访问一致性** | 索引 `k` 始终表示跳过 `k` 个元素,逻辑清晰 | 多维数组 `a[i][j]` 地址计算:<br> `base + (i*col + j)*size` 无修正项 | --- ### 三、与其他设计的对比 ```c // 索引从 0 开始 (C/C++/Java/Python) int arr[3] = {10, 20, 30}; arr[0] = 10; // 地址 = base + 0 // 索引从 1 开始 (Pascal/Matlab) var arr: array[1..3] of integer; arr[1] := 10; // 地址 = base + (1-1)*size → 仍需隐含的零偏移 ``` > 💡 关键结论:**从 0 开始的索引是物理内存模型的直接反映**,避免了逻辑层与物理层的转换开销[^4]。 --- ### 四、对算法的影响 1. **二分查找边界简化** ```c while (low <= high) { // 0 起始索引边界清晰 mid = low + (high - low)/2; if (arr[mid] == target) return mid; // ... } ``` 若索引从 1 开始,`mid` 计算需额外调整。 2. **位运算优化** 计算二维坐标 `(i,j)` 的线性地址时: $$ \text{index} = i \times \text{cols} + j $$ 无偏移修正,可直接用位运算替代乘法(当 `cols=2^k` 时)[^1]。 --- ### 五、总结:设计哲学 | **设计选择** | **本质** | **优势领域** | |--------------------|---------------------------------------|----------------------| | **索引从 0 开始** | 硬件内存模型的直接抽象 | 系统编程、高性能计算 | | **索引从 1 开始** | 人类计数习惯的映射(需额外转换) | 数学建模、教学演示 | > ⚙️ **核心思想**:C/C++ 作为系统级语言,优先选择**零开销抽象**(Zero-overhead Abstraction),索引从 0 开始是硬件友好性与性能最优化的必然结果[^1][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值