为何C字符串采用零终止字符数组实现而非显式存储长度?
这个问题我刚学C的时候也纠结了好久!毕竟从效率上看,显式存长度的方案确实香——O(1)就能拿到长度,不用傻乎乎遍历整个字符串,怎么看都比零终止的O(n)靠谱。但C偏偏选了零终止,其实得回到它诞生的时代背景和设计逻辑来看:
抠门的硬件时代逼出来的选择:C是70年代在PDP-11上搞出来的,那时候内存贵得离谱,每一个字节都要精打细算。显式存长度需要额外的1到4字节(取决于用
char还是int存),对于短字符串来说,这个额外开销的占比高得吓人。而零终止是复用了字符串本身的结束位置,不用额外占空间,在当时内存紧张的环境下,这是非常务实的最优解。和Unix系统绑定太深:C从出生起就是为写Unix系统服务的。Unix里的很多I/O操作(比如
read/write)都是基于字节流的,零终止字符串天然适配这种“读到结束符就停”的逻辑。而且早期Unix的工具链(汇编器、编译器、各种系统工具)都是围绕零终止字符串做的,沿用这个方案的开发成本比重新搞一套低太多。符合C的极简灵活哲学:零终止字符串本质就是普通的字符数组,你可以直接用数组的所有操作来处理它——随便截取、拼接(只要自己管好内存),不需要额外的结构体包装。而显式存长度的话,通常得搞个结构体(比如
struct { int len; char data[]; }),操作起来多了一层封装,不符合C“贴近硬件、尽量少做抽象”的设计理念。向后兼容的沉重包袱:一旦零终止成为标准,几十年的代码、库、工具都基于它构建了。就算后来硬件内存不再紧张,也很难推翻重来——要兼容这么多旧代码,成本高到无法想象。
当然,你说的缺点确实存在:取长度要线性遍历,还容易因为忘了加终止符或者越界写导致缓冲区溢出。所以后来很多现代语言(比如C++的std::string、Go的string)都改用了“长度+数据”的结构,但C作为元老级语言,只能带着这个历史选择继续走下去。
内容的提问来源于stack exchange,提问作者Nisba

