删不掉的 nul:AI 编程助手在 Windows 上的一次跨平台口误

用 AI 编程助手(Claude Code、ZCode 这类 agent)在 Windows 上干活的人,迟早会在某个项目根目录捡到一个幽灵:一个 0 字节的文件,名字叫 nul。git status 里躺着个 ?? nul,资源管理器里看得见,右键删除却报"找不到该项目"。看得见,删不掉,标准的幽灵文件。 它不是病毒,不是 bug,是 AI 助手的一句跨平台口误。 口误发生在哪一句 AI 助手在 Windows 上跑命令,走的通常是 Git Bash——一个 POSIX 语义的 shell。而模型执行命令时有个职业习惯:把无关的日志和报错重定向丢弃,保持输出干净。丢弃输出的写法在两个世界长得不一样: Windows cmd: some.exe >nul (nul 是设备,输出进了黑洞) Bash: some.exe >/dev/null (/dev/null 是设备,输出进了黑洞) 模型识别到宿主是 Windows,出于好意切了 cmd 方言,写下 >nul。但实际执行这句的 shell 是 Git Bash。在 Bash 眼里,nul 不是设备,就是一个普通的文件名——重定向的目标文件不存在时 Bash 会怎么办?创建它。于是磁盘上多了这么个东西。 一句字面级别的口误:语法是 cmd 的,解释器是 Bash 的。两边单独看都没错,拼在一起就生成了一个不属于任何一方意图的文件。 为什么看得见、删不掉 nul 是 DOS 时代的设备保留名,同一家族还有 con、prn、aux、com1 到 com9、lpt1 到 lpt9。Windows 的 Win32 层在解析路径时,会把出现在文件名位置上的这些名字别名到设备对象上。所以资源管理器、del nul、以及一切走 Win32 路径的程序,操作这个名字时碰到的其实是设备,不是文件——“找不到该项目"就是这么来的:它找的确实不是那个文件。 ...

2026年9月1日 · 1 min

你说的 CRC 是哪个 CRC:一次上下位机联调翻车实录

前段时间组里上下位机联调。协议文档看起来挺完整:帧头、命令字、数据、帧尾带 CRC16 校验。两边各自实现、各自自测,全都通过;一联调,各种 bug 排着队来。查了好几天,最后发现元凶平平无奇:两边都实现了 CRC16,但从头到尾没人问过一句——是哪个 CRC16。 先说结论:CRC 不是"一个算法",是一个算法族。“加个 CRC 校验"这句话的规格含量,跟"加点辣"差不多——都是辣,川湘黔滇各不一样。 翻车姿势 CRC 变体没对齐时的症状,通常是这三种: 全帧校验失败。发什么错什么,怀疑链从串口时序、大小端、接地一路排查到换线换板子,通信工程师的经典受难路线。其实链路好得很,两边算的压根不是同一个数。 为联调把校验旁路。有人图快,把"校验失败"改成"打条日志继续跑”,业务先跑通了,恢复和补测永远排在下个迭代。校验从此形同虚设,比没有还危险——它给人安全感。 偶发"漏检"玄学。压力测试几百万帧,偶尔一两帧校验通过但数据是错的。于是开始研究 CRC 漏检率、查电磁干扰、上磁环。其实是两套算法在 65536 个值里随缘撞上了——不是漏检,是压根没检。 CRC 是一个算法族 CRC 除了"用哪个多项式做除法"这个自由度,除法前后还有一串参数。业界通用的描述模型叫 Rocksoft 模型,出自 Ross Williams 1993 年那篇 A Painless Guide to CRC Error Detection Algorithms,一共五个参数: width:位宽 poly:多项式 init:寄存器初值 refin / refout:输入、输出是否按位反转 xorout:结果异或值 “CRC16” 只锁定了第一个参数,剩下四个全开放。同一个多项式能配出多少种不同结果?看下表(check 是官方测试向量:9 字节 ASCII 字符串 "123456789" 的校验值): 名字 poly init refin/refout xorout check CRC-16/XMODEM 0x1021 0x0000 否/否 0x0000 0x31C3 CRC-16/CCITT-FALSE 0x1021 0xFFFF 否/否 0x0000 0x29B1 CRC-16/KERMIT 0x1021 0x0000 是/是 0x0000 0x2189 CRC-16/ARC 0x8005 0x0000 是/是 0x0000 0xBB3D CRC-16/MODBUS 0x8005 0xFFFF 是/是 0x0000 0x4B37 CRC-32/ISO-HDLC 0x04C11DB7 0xFFFFFFFF 是/是 0xFFFFFFFF 0xCBF43926 前三个共享多项式 0x1021,check 是三个毫不相干的数;中间两个只差一个 init(0x0000 与 0xFFFF),结果就分道扬镳。网上抄的 crc16.c,十份里能抄出五种参数组合,而且多半不写注释。 ...

2026年8月18日 · 1 min

Qt Creator 底下那个“控制台”是假的:一次 Windows 中文乱码排查实录

前段时间写 loghandler,需求说出来特别简单:console 里看的、调试输出里看的、实际落盘的内容,编码得一致。就这么点要求,被中文乱码折腾了好久。各种组合来回试过,才定位到元凶不是代码,而是观察工具本身:Qt Creator 底部那个长得像控制台的窗格,其实不是控制台,是 IDE 重定向、转述过的 output。 先把结论放在最前面: 那个窗格的大名是"应用程序输出"(Application Output)。Qt Creator 运行程序时不给它开控制台,而是用管道接住进程的 stdout/stderr,读出字节后由 IDE 自己解码再显示。它是转述者,不是终端。 所以 SetConsoleOutputCP() 对它完全无效——那是设置真控制台渲染解码的 API,而这条链路上压根没有"控制台渲染"这一环。 printf 的字节流会被窗格按系统本地代码页(中文 Windows 即 GBK)固定解码。源文件是 UTF-8 的话,printf 直出中文在这个窗格里必乱,怎么设置都救不回来。 更迷惑的是 qDebug 走的是另一条 Unicode 通道,在窗格里永远正常。“qDebug 好的、printf 坏的"这个组合,会把你引向一连串错误归因。 以下实测基于 Qt 5 + Qt Creator(起码大版本 5 是这样)。只要 Creator 还是"管道重定向 + 自己解码"这个架构,这个坑就大概率还在;不同版本的解码细节可能变化,所以文末的验证方法比这里的结论更重要。 症状:qDebug 正常,printf 乱码 程序里两种输出混用——qDebug 做调试,printf/fprintf 是老代码或第三方库。在 Qt Creator 里一跑:qDebug 的中文正常,printf 的中文全是 涓枃娴嬭瘯。 这类乱码不用背规律,看长相查表就能反推出"输的什么码、解的什么码”。GitHub 上这份 unicode-encoding-error-table 按外观特征收录了常见错法,涓枃娴嬭瘯 这种"古文码"就是 UTF-8 字节被按 GBK 解读——3 字节的汉字被拆成 2+1 错配,长度都对不上,看起来自然乱得不像话。反过来说,看到它就知道:输出端发的是 UTF-8,解码端在用 GBK。 ...

2026年8月17日 · 2 min