<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>踩坑 on 我的博客</title><link>https://diankao.github.io/tags/%E8%B8%A9%E5%9D%91/</link><description>Recent content in 踩坑 on 我的博客</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 01 Sep 2026 10:27:17 +0800</lastBuildDate><atom:link href="https://diankao.github.io/tags/%E8%B8%A9%E5%9D%91/index.xml" rel="self" type="application/rss+xml"/><item><title>删不掉的 nul：AI 编程助手在 Windows 上的一次跨平台口误</title><link>https://diankao.github.io/posts/nul-ghost-file/</link><pubDate>Tue, 01 Sep 2026 10:27:17 +0800</pubDate><guid>https://diankao.github.io/posts/nul-ghost-file/</guid><description>&lt;p&gt;用 AI 编程助手（Claude Code、ZCode 这类 agent）在 Windows 上干活的人，迟早会在某个项目根目录捡到一个幽灵：一个 0 字节的文件，名字叫 &lt;code&gt;nul&lt;/code&gt;。&lt;code&gt;git status&lt;/code&gt; 里躺着个 &lt;code&gt;?? nul&lt;/code&gt;，资源管理器里看得见，右键删除却报&amp;quot;找不到该项目&amp;quot;。看得见，删不掉，标准的幽灵文件。&lt;/p&gt;
&lt;p&gt;它不是病毒，不是 bug，是 AI 助手的一句&lt;strong&gt;跨平台口误&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="口误发生在哪一句"&gt;口误发生在哪一句&lt;/h2&gt;
&lt;p&gt;AI 助手在 Windows 上跑命令，走的通常是 Git Bash——一个 POSIX 语义的 shell。而模型执行命令时有个职业习惯：把无关的日志和报错重定向丢弃，保持输出干净。丢弃输出的写法在两个世界长得不一样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Windows cmd： some.exe &amp;gt;nul （nul 是设备，输出进了黑洞）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bash： some.exe &amp;gt;/dev/null （/dev/null 是设备，输出进了黑洞）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;模型识别到宿主是 Windows，出于好意切了 cmd 方言，写下 &lt;code&gt;&amp;gt;nul&lt;/code&gt;。但实际执行这句的 shell 是 Git Bash。在 Bash 眼里，&lt;code&gt;nul&lt;/code&gt; 不是设备，就是一个普通的文件名——重定向的目标文件不存在时 Bash 会怎么办？创建它。于是磁盘上多了这么个东西。&lt;/p&gt;
&lt;p&gt;一句字面级别的口误：&lt;strong&gt;语法是 cmd 的，解释器是 Bash 的&lt;/strong&gt;。两边单独看都没错，拼在一起就生成了一个不属于任何一方意图的文件。&lt;/p&gt;
&lt;h2 id="为什么看得见删不掉"&gt;为什么看得见、删不掉&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;nul&lt;/code&gt; 是 DOS 时代的设备保留名，同一家族还有 &lt;code&gt;con&lt;/code&gt;、&lt;code&gt;prn&lt;/code&gt;、&lt;code&gt;aux&lt;/code&gt;、&lt;code&gt;com1&lt;/code&gt; 到 &lt;code&gt;com9&lt;/code&gt;、&lt;code&gt;lpt1&lt;/code&gt; 到 &lt;code&gt;lpt9&lt;/code&gt;。Windows 的 Win32 层在解析路径时，会把出现在文件名位置上的这些名字&lt;strong&gt;别名到设备对象&lt;/strong&gt;上。所以资源管理器、&lt;code&gt;del nul&lt;/code&gt;、以及一切走 Win32 路径的程序，操作这个名字时碰到的其实是设备，不是文件——&amp;ldquo;找不到该项目&amp;quot;就是这么来的：它找的确实不是那个文件。&lt;/p&gt;</description></item><item><title>你说的 CRC 是哪个 CRC：一次上下位机联调翻车实录</title><link>https://diankao.github.io/posts/which-crc-do-you-mean/</link><pubDate>Tue, 18 Aug 2026 11:21:06 +0800</pubDate><guid>https://diankao.github.io/posts/which-crc-do-you-mean/</guid><description>&lt;p&gt;前段时间组里上下位机联调。协议文档看起来挺完整：帧头、命令字、数据、帧尾带 CRC16 校验。两边各自实现、各自自测，全都通过；一联调，各种 bug 排着队来。查了好几天，最后发现元凶平平无奇：&lt;strong&gt;两边都实现了 CRC16，但从头到尾没人问过一句——是哪个 CRC16&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先说结论：CRC 不是&amp;quot;一个算法&amp;quot;，是一个算法族。&amp;ldquo;加个 CRC 校验&amp;quot;这句话的规格含量，跟&amp;quot;加点辣&amp;quot;差不多——都是辣，川湘黔滇各不一样。&lt;/p&gt;
&lt;h2 id="翻车姿势"&gt;翻车姿势&lt;/h2&gt;
&lt;p&gt;CRC 变体没对齐时的症状，通常是这三种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;全帧校验失败&lt;/strong&gt;。发什么错什么，怀疑链从串口时序、大小端、接地一路排查到换线换板子，通信工程师的经典受难路线。其实链路好得很，两边算的压根不是同一个数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为联调把校验旁路&lt;/strong&gt;。有人图快，把&amp;quot;校验失败&amp;quot;改成&amp;quot;打条日志继续跑&amp;rdquo;，业务先跑通了，恢复和补测永远排在下个迭代。校验从此形同虚设，比没有还危险——它给人安全感。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;偶发&amp;quot;漏检&amp;quot;玄学&lt;/strong&gt;。压力测试几百万帧，偶尔一两帧校验通过但数据是错的。于是开始研究 CRC 漏检率、查电磁干扰、上磁环。其实是两套算法在 65536 个值里随缘撞上了——不是漏检，是压根没检。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="crc-是一个算法族"&gt;CRC 是一个算法族&lt;/h2&gt;
&lt;p&gt;CRC 除了&amp;quot;用哪个多项式做除法&amp;quot;这个自由度，除法前后还有一串参数。业界通用的描述模型叫 Rocksoft 模型，出自 Ross Williams 1993 年那篇 A Painless Guide to CRC Error Detection Algorithms，一共五个参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;width&lt;/strong&gt;：位宽&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;poly&lt;/strong&gt;：多项式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;init&lt;/strong&gt;：寄存器初值&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;refin / refout&lt;/strong&gt;：输入、输出是否按位反转&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;xorout&lt;/strong&gt;：结果异或值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;CRC16&amp;rdquo; 只锁定了第一个参数，剩下四个全开放。同一个多项式能配出多少种不同结果？看下表（check 是官方测试向量：9 字节 ASCII 字符串 &lt;code&gt;&amp;quot;123456789&amp;quot;&lt;/code&gt; 的校验值）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名字&lt;/th&gt;
&lt;th&gt;poly&lt;/th&gt;
&lt;th&gt;init&lt;/th&gt;
&lt;th&gt;refin/refout&lt;/th&gt;
&lt;th&gt;xorout&lt;/th&gt;
&lt;th&gt;check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CRC-16/XMODEM&lt;/td&gt;
&lt;td&gt;0x1021&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;否/否&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;0x31C3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-16/CCITT-FALSE&lt;/td&gt;
&lt;td&gt;0x1021&lt;/td&gt;
&lt;td&gt;0xFFFF&lt;/td&gt;
&lt;td&gt;否/否&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;0x29B1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-16/KERMIT&lt;/td&gt;
&lt;td&gt;0x1021&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;是/是&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;0x2189&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-16/ARC&lt;/td&gt;
&lt;td&gt;0x8005&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;是/是&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;0xBB3D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-16/MODBUS&lt;/td&gt;
&lt;td&gt;0x8005&lt;/td&gt;
&lt;td&gt;0xFFFF&lt;/td&gt;
&lt;td&gt;是/是&lt;/td&gt;
&lt;td&gt;0x0000&lt;/td&gt;
&lt;td&gt;0x4B37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-32/ISO-HDLC&lt;/td&gt;
&lt;td&gt;0x04C11DB7&lt;/td&gt;
&lt;td&gt;0xFFFFFFFF&lt;/td&gt;
&lt;td&gt;是/是&lt;/td&gt;
&lt;td&gt;0xFFFFFFFF&lt;/td&gt;
&lt;td&gt;0xCBF43926&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;前三个共享多项式 0x1021，check 是三个毫不相干的数；中间两个只差一个 init（0x0000 与 0xFFFF），结果就分道扬镳。网上抄的 crc16.c，十份里能抄出五种参数组合，而且多半不写注释。&lt;/p&gt;</description></item><item><title>Qt Creator 底下那个“控制台”是假的：一次 Windows 中文乱码排查实录</title><link>https://diankao.github.io/posts/qtcreator-fake-console-encoding-trap/</link><pubDate>Mon, 17 Aug 2026 17:48:32 +0800</pubDate><guid>https://diankao.github.io/posts/qtcreator-fake-console-encoding-trap/</guid><description>&lt;p&gt;前段时间写 loghandler，需求说出来特别简单：console 里看的、调试输出里看的、实际落盘的内容，编码得一致。就这么点要求，被中文乱码折腾了好久。各种组合来回试过，才定位到元凶不是代码，而是观察工具本身：&lt;strong&gt;Qt Creator 底部那个长得像控制台的窗格，其实不是控制台，是 IDE 重定向、转述过的 output&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先把结论放在最前面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;那个窗格的大名是&amp;quot;应用程序输出&amp;quot;（Application Output）。Qt Creator 运行程序时不给它开控制台，而是用管道接住进程的 stdout/stderr，读出字节后&lt;strong&gt;由 IDE 自己解码&lt;/strong&gt;再显示。它是转述者，不是终端。&lt;/li&gt;
&lt;li&gt;所以 &lt;code&gt;SetConsoleOutputCP()&lt;/code&gt; 对它&lt;strong&gt;完全无效&lt;/strong&gt;——那是设置真控制台渲染解码的 API，而这条链路上压根没有&amp;quot;控制台渲染&amp;quot;这一环。&lt;/li&gt;
&lt;li&gt;printf 的字节流会被窗格按系统本地代码页（中文 Windows 即 GBK）固定解码。源文件是 UTF-8 的话，printf 直出中文在这个窗格里&lt;strong&gt;必乱&lt;/strong&gt;，怎么设置都救不回来。&lt;/li&gt;
&lt;li&gt;更迷惑的是 qDebug 走的是另一条 Unicode 通道，在窗格里&lt;strong&gt;永远正常&lt;/strong&gt;。&amp;ldquo;qDebug 好的、printf 坏的&amp;quot;这个组合，会把你引向一连串错误归因。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下实测基于 Qt 5 + Qt Creator（起码大版本 5 是这样）。只要 Creator 还是&amp;quot;管道重定向 + 自己解码&amp;quot;这个架构，这个坑就大概率还在；不同版本的解码细节可能变化，所以文末的验证方法比这里的结论更重要。&lt;/p&gt;
&lt;h2 id="症状qdebug-正常printf-乱码"&gt;症状：qDebug 正常，printf 乱码&lt;/h2&gt;
&lt;p&gt;程序里两种输出混用——qDebug 做调试，printf/fprintf 是老代码或第三方库。在 Qt Creator 里一跑：qDebug 的中文正常，printf 的中文全是 &lt;code&gt;涓枃娴嬭瘯&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这类乱码不用背规律，看长相查表就能反推出&amp;quot;输的什么码、解的什么码&amp;rdquo;。GitHub 上这份 &lt;a href="https://github.com/justjavac/unicode-encoding-error-table"&gt;unicode-encoding-error-table&lt;/a&gt; 按外观特征收录了常见错法，&lt;code&gt;涓枃娴嬭瘯&lt;/code&gt; 这种&amp;quot;古文码&amp;quot;就是 UTF-8 字节被按 GBK 解读——3 字节的汉字被拆成 2+1 错配，长度都对不上，看起来自然乱得不像话。反过来说，看到它就知道：输出端发的是 UTF-8，解码端在用 GBK。&lt;/p&gt;</description></item></channel></rss>