昨天下午我在厨房泡了一壶凤凰单丛,第三泡刚倒进杯子,手机响了。是老周,他在电话那头声音发虚,说兄弟你快帮我看看,我这链表插入怎么一跑就段错误。我端着茶坐回电脑前,把他发来的那段 insert 代码粘进编辑器,窗口一最大化,整个人就钻进那个第一视角里去了。
说真的,写 C 语言里插入节点的那几行代码,眼睛看到的东西和别人以为你看到的东西,完全是两码事。
老周那段代码我到现在还记得,第 6 行写的是 node->next = prev->next;,第 7 行 prev->next = node;,顺序倒是没写反。问题出在第 5 行,他先把 node 的 data 用 malloc 分配了 32 个字节,但 node 本身还是个没初始化的栈指针。我盯着那行看了能有二十秒,茶都凉了半口。然后我用鼠标把光标点在 malloc 那一行上,按下 Ctrl+F5,断点停在 node 的地址上,值是一串 0xcccccccc。那个瞬间,我几乎能听见自己心里"啊"了一声。
指针这东西,它不跟你讲道理,它只给你看地址。
我顺手在页面上翻到老周半年前发的一篇笔记,标题叫特警爸爸每日爆肝儿子,点开一看,写的居然是他带儿子去科技馆看机械臂,回来路上儿子问他"爸爸你上班是不是就是让机器人听话"。老周在那篇里写,他当时没答上来,因为回家路上他正想着自己白天调的那个 bug。我看完乐了半天,觉得这人写代码和生活都一个调调:看起来在讲别的事,其实全在讲同一件事。
说回插入。我在第一视角里看到的顺序是这样的:先是屏幕左上角那行灰色的 #include <stdio.h>,然后是中间那段缩进得有点歪的 insert 函数,光标停在 node->next 的箭头中间。接着我按了一下 F10,单步跳过,监视窗口里 prev 的值从 0x7ffd... 跳了一下。那一刻我脑子里没什么宏大的东西,就一个念头:这个箭头到底指向谁。真的,就这一句。
我把这段写下来不是教你修 bug,是想说,插入时那个第一视角,其实是"我正拿着一个还没检查过的地址,准备把它连到一条已经存在的链上"。
修完老周的代码我又顺手翻了两篇旧文。有一篇是去年冬天写的,讲一个在日本做家庭教师的朋友,页面叫家庭教師からのova,里面提到他第一次给学生讲指针,用的是地铁线路图,说"你看,每一站都有一个出口编号,但出口通不通,得走出去才知道"。我当时在文末写了一句"这比喻不精确但够用",现在想想,自己也挺爱跑题的。
说实话,我得在这儿说一句可能不太好听的:很多 C 语言教程把插入写得像搭积木,先这块后那块,步骤清晰,其实那不是第一视角。第一视角是你在屏幕前,知道链表的头节点在内存某个地方,知道 malloc 可能失败,知道 prev 可能是 NULL,但你还是得硬着头皮往下写,边写边在心里祈祷。教程是给你看结果,第一视角是给你看过程里的犹豫。我个人的观点是,那种犹豫才是真正值得记录的东西,比一个完美无 bug 的答案更接近真实。
换句话说,我主张:写插入时,允许自己承认不确定。
写完这些我看了眼时间,四点出头,茶已经彻底凉了,第四泡还没倒。我把老周的代码保存了一份到他的共享目录,备注写的是"指针先归零,再谈连接"。然后我翻了翻收藏夹,看到一篇很早以前存的永久免费mv高清免费看国内,其实跟代码没半点关系,但那篇的排版干净,标题字体我挺喜欢,就一直留着。作为编辑,我得承认我对排版的好感有时候超过内容本身,这点不太好,但改不掉。
天快暗的时候我在书架上摸到一本旧册子,是本讲配色的,叫国内黄色发色大全,里面有一页专门讲暖黄和土棕怎么配,我翻了两眼,忽然觉得今天这页面背景的 #fff9f2 和主色 #c65f2a 就是从那儿来的灵感。可能吧,也可能只是巧合。
好了,跑题够远了。回到那个第一视角,它其实很安静。
最后那段插入代码跑通的时候,监视窗口里 node->next 终于不再是 0xcccccccc,而是一个像模像样的地址。老周在电话那头说"成了成了",我嗯了一声,把茶杯端起来发现是空的。这大概就是我今天想写下来的全部:不是指针的道理,是下午三点十七分到四点出头那一段,眼睛盯着屏幕,手指在键盘上,心里知道有一个箭头马上要指向对的地方。