<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 静态代码分析</title>
	<atom:link href="http://tonybai.com/tag/%e9%9d%99%e6%80%81%e4%bb%a3%e7%a0%81%e5%88%86%e6%9e%90/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 15 Jun 2026 00:16:08 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>火爆外网的 Go 开源神器 CLI Printing Press：一键生成 Agent 专属 CLI 工具</title>
		<link>https://tonybai.com/2026/05/09/cli-printing-press-intro/</link>
		<comments>https://tonybai.com/2026/05/09/cli-printing-press-intro/#comments</comments>
		<pubDate>Fri, 08 May 2026 23:03:53 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgentNative]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[AI智能体]]></category>
		<category><![CDATA[Automation]]></category>
		<category><![CDATA[BackwardCompatibility]]></category>
		<category><![CDATA[BSR]]></category>
		<category><![CDATA[BufSchemaRegistry]]></category>
		<category><![CDATA[CLIPrintingPress]]></category>
		<category><![CDATA[CodeFactory]]></category>
		<category><![CDATA[CodeLinting]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguage]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[IDL]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Minimalism]]></category>
		<category><![CDATA[ModelContextProtocol]]></category>
		<category><![CDATA[PerformanceOptimization]]></category>
		<category><![CDATA[protocol]]></category>
		<category><![CDATA[RemotePlugins]]></category>
		<category><![CDATA[StaticAnalysis]]></category>
		<category><![CDATA[StaticCompilation]]></category>
		<category><![CDATA[ToolEngineering]]></category>
		<category><![CDATA[UnixPhilosophy]]></category>
		<category><![CDATA[Unix哲学]]></category>
		<category><![CDATA[代码工厂]]></category>
		<category><![CDATA[代码规范]]></category>
		<category><![CDATA[协议]]></category>
		<category><![CDATA[向后兼容]]></category>
		<category><![CDATA[工具工程]]></category>
		<category><![CDATA[性能优化]]></category>
		<category><![CDATA[接口描述语言]]></category>
		<category><![CDATA[极简主义]]></category>
		<category><![CDATA[模型上下文协议]]></category>
		<category><![CDATA[自动化]]></category>
		<category><![CDATA[远程插件]]></category>
		<category><![CDATA[静态代码分析]]></category>
		<category><![CDATA[静态编译]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=6284</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/05/09/cli-printing-press-intro 大家好，我是Tony Bai。 近日，一个名叫 cli-printing-press 的开源项目冲上了 X.com 热搜。它用 Go 写成，解决的是 AI Agent 时代最隐秘、也最致命的痛点——工具不够用，更不好用。 先说一个反常识的故事 Discord 有 300 多个官方 API 端点。 按常理，一个覆盖所有端点的 CLI 工具，应该是最好用的那个。但事实恰恰相反。 OpenClaw 之父 Peter Steinberger 用 Go 写了一个叫 discrawl 的工具，只提供 11 个命令：sync、search、sql、tail、mentions、members……就这些。结果？700多 颗 GitHub Star，社区口口相传，被无数 AI Agent 开发者列为必装工具。 为什么一个”阉割版”打败了”全功能版”？ 因为 Steinberger 看到了 Discord API 设计者自己都没意识到的东西：聊天记录不只是聊天，它是一个组织的知识库。 每一条消息线程，本质上都是一份可以被归档、被索引、被本地全文搜索的文档。那 11 个命令，围绕的就是这个洞察。300 个端点包装器，做不到这一点。 CLI Printing [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/cli-printing-press-intro-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/05/09/cli-printing-press-intro">本文永久链接</a> &#8211; https://tonybai.com/2026/05/09/cli-printing-press-intro</p>
<p>大家好，我是Tony Bai。</p>
<blockquote>
<p>近日，一个名叫 <a href="https://github.com/mvanhorn/cli-printing-press">cli-printing-press 的开源项目</a>冲上了 X.com 热搜。它用 Go 写成，解决的是 AI Agent 时代最隐秘、也最致命的痛点——工具不够用，更不好用。</p>
</blockquote>
<h2>先说一个反常识的故事</h2>
<p>Discord 有 300 多个官方 API 端点。</p>
<p>按常理，一个覆盖所有端点的 CLI 工具，应该是最好用的那个。但事实恰恰相反。</p>
<p>OpenClaw 之父 Peter Steinberger 用 Go 写了一个叫 <a href="https://github.com/steipete/discrawl">discrawl</a> 的工具，只提供 11 个命令：sync、search、sql、tail、mentions、members……就这些。结果？700多 颗 GitHub Star，社区口口相传，被无数 AI Agent 开发者列为必装工具。</p>
<p>为什么一个”阉割版”打败了”全功能版”？</p>
<p>因为 Steinberger 看到了 Discord API 设计者自己都没意识到的东西：<strong>聊天记录不只是聊天，它是一个组织的知识库。</strong></p>
<p>每一条消息线程，本质上都是一份可以被归档、被索引、被本地全文搜索的文档。那 11 个命令，围绕的就是这个洞察。300 个端点包装器，做不到这一点。</p>
<p>CLI Printing Press，就是一台把这种洞察自动化的“机器”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agentic-software-engineering-qr.png" alt="" /></p>
<h2>AI Agent 的”工具饥渴”时代</h2>
<p>在聊这个工具之前，我们需要先理解 2026 年的 AI 开发现状。</p>
<p>Claude Code、Codex、OpenClaw、Gemini CLI等 AI Agent 的能力已经突飞猛进。它们可以写代码、查数据、做分析、自主决策。但有一个瓶颈正在成为所有人的噩梦：<strong>现有的 CLI 工具，根本不是为 Agent 设计的。</strong></p>
<p>想象一下 Agent 在调用一个普通 CLI 时会遇到什么：</p>
<ul>
<li>输出格式不稳定，有时是表格，有时是纯文本，Agent 根本无法可靠地解析；</li>
<li>没有类型化退出码，出了错要去解析 stderr 的文字才能知道是认证失败还是网络超时；</li>
<li>每次查询都要远程 API 调用，一个复合问题需要十几次 round-trip，token 哗哗地烧；</li>
<li>遇到没有公开 API 文档的网站（比如 ESPN、Google Flights），完全束手无策。</li>
</ul>
<p>CLI Printing Press 项目 README 开篇就把这个痛点说得很直白：<strong>“在 AI Agent 的世界里，没有什么比时间和金钱更宝贵——落到工程层面，就是速度和 token 消耗。一个设计优良的 CLI 是 Agent 的肌肉记忆：不用翻文档，不走弯路，不浪费 token。”</strong></p>
<p>CLI Printing Press，就是为了解决这个问题而生的。</p>
<h2>它到底是什么？</h2>
<p>用一句话描述：</p>
<blockquote>
<p><strong>CLI Printing Press 是一台 CLI 工厂。给它一个 API 地址（或者任意一个网站），它输出一个专门为 AI Agent 设计的 Go CLI 工具 + MCP 服务器 + Claude Code Skill。</strong></p>
</blockquote>
<p>安装方式极其简单（Go需要>=1.26版本）：</p>
<pre><code class="bash"># 安装工厂本体
go install github.com/mvanhorn/cli-printing-press/v4/cmd/printing-press@latest

# 克隆技能文件（配合 Claude Code 使用）
git clone https://github.com/mvanhorn/cli-printing-press.git

# 在 Claude Code 中启动，直接加载skill
claude --plugin-dir .
</code></pre>
<p>然后在 Claude Code 中，一条命令就能启动生产流程：</p>
<pre><code>/printing-press Notion          # 给 Notion API 生成 CLI
/printing-press https://espn.com/nba  # 直接指向网站，无需 API 文档
</code></pre>
<h2>为什么选 Go？</h2>
<p>这是一个值得细聊的设计决策。</p>
<p>在这个 TypeScript、Python 等生产力语言大行其道的时代，CLI Printing Press 选择了 Go，并且坚定地把 Go 作为所有生成产物的语言。原因很现实：</p>
<p><strong>第一，分发极其简单。</strong> go install 一行命令，跨平台，无依赖。Agent 在运行时动态安装工具，最怕的就是依赖地狱。Go 的静态编译二进制文件是最优解。</p>
<p><strong>第二，Go 已经被实践证明。</strong> Peter Steinberger 用 Go 写的 <a href="https://github.com/openclaw/gogcli">gogcli</a>（Google Workspace CLI）拥有 7000+ Star，而 Google 官方之后推出的 Rust 版本，一周冲到 1 万 Star，却在社区中败给了前者。一个用户的评价是：”我 100% 偏好 gogcli，因为它就是能让 Agent 做到它需要做的事。”<strong>广度没能打败深度，Rust 没能打败 Go。</strong></p>
<p><strong>第三，Go 的并发模型非常适合 Agent 的使用场景。</strong> SQLite 批量事务、并发 sync worker、FTS5 全文索引……这些都是 Agent 高频调用场景下的性能关键路径，Go 处理起来得心应手。</p>
<h2>核心机制：它如何做到的？</h2>
<h3>每个 API 都有非显见身份（Non-Obvious Insight）</h3>
<p>这是整个项目最有哲学深度的设计。</p>
<p>Printing Press 在生成任何 CLI 之前，都要先找到这个 API 的”非显见洞察”（NOI），一句话的格式：</p>
<blockquote>
<p>“[API] 不只是 [显而易见的功能]。它是 [非显见的东西]。每个 [数据点] 都是关于 [隐藏真相] 的信号。”</p>
</blockquote>
<p>几个例子，读完你可能会有点震撼：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/cli-printing-press-intro-2.png" alt="" /></p>
<p>这个 NOI 是整个 CLI 的创意 DNA。如果 AI 在研究阶段写不出一个 NOI，说明研究深度不够，Phase 0 不会放行。</p>
<h3>五层创造力梯子</h3>
<p>大多数工具停在第 1 层。Printing Press 直接爬到第 5 层。</p>
<pre><code>第 1 层：API 端点包装命令         ← 99% 的生成工具止步于此
第 2 层：输出格式 (--json, --csv)
第 3 层：本地持久化 (sync, search, SQLite)
第 4 层：领域分析 (stale, orphans, load)    ← discrawl 的水准
第 5 层：行为洞察 (health 综合评分, similar 重复检测)  ← 目前无人到达
</code></pre>
<p>第 3 层以上，才是真正的价值所在。一旦数据落在本地 SQLite，compound 查询就成为可能——这是任何无状态 API 包装器永远做不到的事情。</p>
<h3>本地优先数据层</h3>
<p>Printing Press 生成的每个高质量 CLI，都带有一套完整的本地数据层：</p>
<ul>
<li>领域特定的 SQLite 表（不是 JSON blob，是真正的关系型结构）</li>
<li>FTS5 全文搜索索引</li>
<li>带游标追踪的增量同步</li>
<li>直接 SQL 查询接口</li>
</ul>
<p>这意味着什么？看一个 Linear 的真实 Demo：</p>
<pre><code class="bash">$ /pp-linear sql 'blocked issues whose blocker hasn't moved in 7 days'
</code></pre>
<p>背后执行的是：</p>
<pre><code class="sql">SELECT i.identifier, i.title, age(now(), b.updated_at) AS stuck
FROM issues i
JOIN issue_relations r ON r.issue_id = i.id
JOIN issues b ON b.id = r.related_issue_id
WHERE r.type = 'blocked_by'
  AND b.state = 'in_progress'
  AND b.updated_at &lt; now() - interval '7 days';
</code></pre>
<p>结果：</p>
<pre><code>ENG-412  Crash on cold-start        blocked 11d
ENG-388  Reconnect dropped sockets  blocked 9d
ENG-301  Backfill missing rows       blocked 8d
</code></pre>
<p>50 毫秒。本地完成。关键是 Linear 的官方 API 无法回答这个问题。</p>
<h3>Agent-Native 设计哲学</h3>
<p>这是 Printing Press 和普通 CLI 生成工具最根本的区别。每一个生成出来的 CLI，都内置了以下设计：</p>
<ul>
<li><strong>自动 JSON 输出</strong>：终端里显示人性化表格，管道传输时自动切换为 JSON，无需 &#8211;json 标志。</li>
<li><strong>&#8211;compact 模式</strong>：只返回高重力字段（id、name、status、时间戳），减少 <strong>60-80%</strong> 的 token 消耗。</li>
<li><strong>&#8211;dry-run 安全探索</strong>：让 Agent 在不执行副作用的情况下验证命令逻辑。</li>
<li><strong>类型化退出码</strong>：</li>
</ul>
<pre><code>- 0 = 成功
- 2 = 用法错误
- 3 = 资源未找到
- 4 = 认证失败
- 5 = API 错误
- 7 = 速率限制
</code></pre>
<p>Agent 读一个退出码就知道下一步怎么做，不需要解析错误文字，自我纠正只需一次重试。</p>
<p><strong>为什么 CLI 比 MCP 更适合 Agent？</strong></p>
<blockquote>
<p>CLI 的 token 消耗比 MCP tool definition 少 100 倍。LLM 本来就在 shell 交互上训练过。退出码 0 = 完成。&#8211;json | jq 是一流的组合模式。</p>
</blockquote>
<p>这套设计哲学有一句精辟的总结：<strong>“Agent-native 设计，就是认真对待 CLI 设计 的结果。”</strong></p>
<h3>无 API 文档？浏览器嗅探搞定</h3>
<p>ESPN 没有官方 API。Google Flights 没有公开文档。Dominos 也没有。</p>
<p>Printing Press 的解法：启动一个浏览器，你正常点击浏览，它在后台抓取所有 HTTP 流量，逆向工程出 API 结构，自动生成 OpenAPI spec，然后继续走后面的生成流程。</p>
<p>三种输入模式，覆盖所有场景：</p>
<ul>
<li>&#8211;spec：直接提供 OpenAPI spec 文件</li>
<li>&#8211;har：DevTools 导出的 HAR 流量包</li>
<li>直接 URL：交给浏览器嗅探</li>
</ul>
<h3>工厂流水线，一次生成，双接口输出</h3>
<p>每次运行 Printing Press，整个流程分阶段进行：</p>
<ul>
<li><strong>Phase 0</strong>：解析 &amp; 复用（1-3 分钟）</li>
<li><strong>Phase 1</strong>：研究简报 — API 身份、竞争对手、数据层、产品论点（5-10 分钟）</li>
<li><strong>Phase 1.5</strong>：生态吸收门 — 目录化每个 MCP/skill/CLI 的功能，生成吸收清单（5-10 分钟）</li>
<li><strong>Phase 1.7</strong>：浏览器嗅探门（2-5 分钟）</li>
<li><strong>Phase 2</strong>：生成 Go CLI + MCP 服务器（1-2 分钟）</li>
<li><strong>Phase 3</strong>：构建 GOAT — 吸收所有功能 + 超越命令（10-20 分钟）</li>
<li><strong>Phase 4</strong>：发货检查 — Dogfood + 验证 + 质量评分（3-8 分钟）</li>
<li><strong>Phase 5</strong>：Live Smoke Test（可选）（2-5 分钟）</li>
</ul>
<p>Printing Press产出的不是一个，而是两个可用工具：</p>
<pre><code>一个 spec 进去
  → &lt;api&gt;-pp-cli    Cobra CLI，供 Claude Code / Codex / shell 调用
  → &lt;api&gt;-pp-mcp    MCP 服务器，供 Claude Desktop / Cursor / Windsurf 使用
</code></pre>
<p>两者共享同一个 internal/client、同一个 internal/store、同一套认证逻辑。零代码重复，一套实现，双场景覆盖。</p>
<h2>质量不靠玄学，靠四项机械验证</h2>
<p>生成出来的 CLI 质量如何保证？Printing Press 用了一套两层 100 分制评分系统，加四项机械化验证。</p>
<p><strong>第一层（基础设施，50分）</strong>：检查骨架是否正确——输出模式、认证流程、错误处理、Agent-Native 标志、终端 UX、README、Doctor 命令、本地缓存。</p>
<p><strong>第二层（领域正确性，50分）</strong>：检查代码是否真的能跑——生成的 URL 路径是否存在于 OpenAPI spec、认证格式是否和 spec 一致、SQLite 数据管道是否正确连通、是否有死代码和悬挂函数。</p>
<p>Grade A = 85 分以上。两层都过，才算合格。</p>
<p>四项行为证明（Proof of Behavior）：</p>
<ul>
<li><strong>路径证明</strong>：所有生成的命令 URL 都存在于 OpenAPI spec</li>
<li><strong>标志证明</strong>：所有注册的 flag 都被至少一个命令引用</li>
<li><strong>管道证明</strong>：每个 SQLite 表都有 WRITE 路径（sync）和 READ 路径（search/query）</li>
<li><strong>认证证明</strong>：认证头格式和 spec 的 securitySchemes 匹配</li>
</ul>
<p>任何一项证明失败，会触发自动修复流程，重新验证。</p>
<h2>已打印的 CLI 库：45 个开箱即用</h2>
<p>不想自己生成？官方已经打印好了 45 个 CLI，覆盖主流场景：</p>
<ul>
<li><strong>旅行</strong>：flight-goat（Kayak + Google Flights 双数据源，一条命令搞定长途航班搜索）</li>
<li><strong>体育</strong>：espn-pp-cli（17 个体育项目，实时比分、系列赛状态、伤病报告）</li>
<li><strong>生产力</strong>：linear-pp-cli（50ms 复合查询）、slack-pp-cli、cal-com-pp-cli</li>
<li><strong>电商</strong>：ebay-pp-cli（真正的狙击竞价）、craigslist-pp-cli（历史价格对比、骗局评分）</li>
<li><strong>房产</strong>：redfin-pp-cli（内部 Stingray API 嗅探，$/sqft 净 HOA 排名）</li>
<li><strong>美食</strong>：dominos-pp-cli（本地最优套餐叠加，这是 Dominos 官网没有的功能）</li>
</ul>
<p>安装方式同样极简：</p>
<pre><code class="bash"># 一键安装入门四件套
npx -y @mvanhorn/printing-press install starter-pack

# 安装指定工具
npx -y @mvanhorn/printing-press install espn sentry linear
</code></pre>
<h2>两个 CLI 协同工作的真实场景</h2>
<p>Printing Press 最打动人的地方，是多个 CLI 可以在同一个 Claude 对话中协同工作。</p>
<p><strong>场景：我想去看 OKC 的季后赛，怎么买最便宜的机票？</strong></p>
<pre><code class="bash">$ /pp-espn nba okc round 2 game 1 + /pp-flightgoat sea-okc, fly-in same day
</code></pre>
<p>两个 CLI，一次对话：</p>
<ul>
<li>espn-pp-cli 拉取实时数据：OKC 刚以 131-122 赢了凤凰城，第二轮第一场预计在 5 月 9 日或 10 日</li>
<li>flightgoat-pp-cli 立刻查询：西雅图飞俄克拉荷马城，当天往返</li>
<li>结果：西南航空 $437 往返，推荐 Wanna Get Away+ 可退款票，Frontier 的那班到得太晚，跳过</li>
</ul>
<p>这不是 Demo，这是真实运行的输出。两个工具各司其职，一个 Agent 对话完成端到端决策。</p>
<h2>写在最后：Go 为什么在 AI 时代逆袭</h2>
<p>CLI Printing Press 的出现和走红，其实折射出一个更大的趋势。</p>
<p>Rust 以性能和安全著称，Python 以生态和易用性著称，但在 AI Agent 工具这个细分赛道，<strong>Go 正在悄悄胜出</strong>。原因很简单：</p>
<ol>
<li><strong>分发成本最低</strong>：单一静态二进制，go install 一行，Agent 可以动态自安装。</li>
<li><strong>并发模型刚好够用</strong>：协程 + channel 处理并发 sync 任务，不过度设计。</li>
<li><strong>SQLite 生态成熟</strong>：go-sqlite3、modernc/sqlite，本地优先架构的标准搭档。</li>
<li><strong>工程师接受度高</strong>：Agent 调用的工具，背后的人类也要维护，Go 的可读性是优势。</li>
</ol>
<p>更深层的洞察是：<strong>AI Agent 需要的不是最强的工具，而是最可靠、更好用的工具。</strong> 打 5 分的输出稳定输出，胜过偶尔打 9 分但不可预测的输出。Go 的 CLI 恰恰提供了这种可靠性。</p>
<p>而 CLI Printing Press，把这套哲学变成了一条流水线。</p>
<p><em>如果你也在构建 AI Agent，或者正在为 Agent 寻找合适的工具层，这个项目值得花半小时认真研究一下。它解决的问题，可能比你意识到的还要根本。</em></p>
<h2>参考资料</h2>
<ul>
<li><strong>项目地址</strong>：<a href="https://github.com/mvanhorn/cli-printing-press">github.com/mvanhorn/cli-printing-press</a></li>
<li><strong>官网</strong>：<a href="https://printingpress.dev">printingpress.dev</a></li>
<li><strong>CLI 库仓库</strong>：<a href="https://github.com/mvanhorn/printing-press-library">github.com/mvanhorn/printing-press-library</a></li>
<li><strong>X热搜</strong>：https://x.com/i/trending/2052445800421015770</li>
</ul>
<hr />
<p><strong>今日互动探讨：</strong></p>
<p>看完这款“CLI 印刷机”，你觉得在 AI 时代，传统的 RESTful API 是否已经走到了尽头？你最想为哪个原本没有 API 的网站“打印”一个专属工具<br />
？</p>
<p>欢迎在评论区分享你的脑洞！</p>
<hr />
<p>还在为写 Agent 框架频频死循环、上下文爆炸而束手无策？我的新专栏 <strong>《<a href="http://gk.link/a/12IzL">从0 开始构建 Agent Harness</a>》</strong> 将带你：</p>
<ul>
<li>抛弃臃肿框架，回归“驾驭工程 (Harness Engineering)”的第一性原理</li>
<li>用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等，复刻极简OpenClaw</li>
<li>构建坚不可摧的 Safety Middleware 与飞书人工审批防线</li>
<li>在底层实现 Token 成本审计、链路追踪与自动化跑分评估</li>
<li>从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”</li>
</ul>
<p>扫描下方二维码，开启从 0 开始构建Agent Harness 的实战之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/build-agent-harness-from-scratch-qr.png" alt="" /></p>
<hr />
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。 </li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/05/09/cli-printing-press-intro/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C,C++开源项目中的100个Bugs</title>
		<link>https://tonybai.com/2013/04/10/100-bugs-in-c-cpp-opensource-projects/</link>
		<comments>https://tonybai.com/2013/04/10/100-bugs-in-c-cpp-opensource-projects/#comments</comments>
		<pubDate>Wed, 10 Apr 2013 01:59:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[Array]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Chromium]]></category>
		<category><![CDATA[Clang]]></category>
		<category><![CDATA[CMake]]></category>
		<category><![CDATA[Code-Analyzer]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Pointer]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[PVS-Studio]]></category>
		<category><![CDATA[ReactOS]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[多级指针]]></category>
		<category><![CDATA[多维数组]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[指针]]></category>
		<category><![CDATA[数组]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[静态代码分析]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1247</guid>
		<description><![CDATA[俄罗斯OOO Program Verification Systems公司用自己的静态源码分析产品PVS-Studio对一些知名的C/C++开源项目，诸如Apache Http Server、Chromium、Clang、CMake、MySQL等的源码进行了分析，找出了100个典型的Bugs。个人觉得这份列表对C/C++ 程序员有一定参考意义。与其说事后用静态工具分析，倒不如在编码时就提高自知自觉，避免这份列表上的错误发生在你的代码中，因此这里将部分摘录一些Bugs（Bug编号这里不连续，为的是对应原文的编号）并做简要说明。原文将这份Bug列表分为了几类，这里也将沿用这个思路。 一、数组和字符串处理错误 数组和字符串处理错误是C/C++程序中最多的一类缺陷类型。这也可以看作是我们为拥有高效地底层内存操作能力而付出的代价。 [#1]&#160;Wolfenstein 3D项目 -&#34;只有部分对象被clear了&#34; void CG_RegisterItemVisuals( int itemNum ) { &#160;&#160;&#160; &#8230; &#160;&#160;&#160; itemInfo_t *itemInfo; &#160;&#160;&#160; &#8230; &#160;&#160;&#160; memset( itemInfo, 0, sizeof( &#38;itemInfo ) ); &#160;&#160;&#160; &#8230; } 这里的Bug出现在memset那一行。代码的真实意图是clear iteminfo这块内存，但调用memset时，第三个参数传入的却是sizeof(&#38;iteminfo)，要知道 sizeof(&#38;itemInfo) != sizeof(itemInfo_t)，前者只是一个指针的大小罢了。正确的写法是： memset(itemInfo, 0, sizeof(itemInfo_t)); 或memset(itemInfo, 0, sizeof(*itemInfo)); [#2]&#160;Wolfenstein 3D项目 -&#34;只有部分Matrix被clear了&#34; ID_INLINE mat3_t::mat3_t( float src[ 3 [...]]]></description>
			<content:encoded><![CDATA[<p style="font-size: 13px;">俄罗斯<a href="http://www.viva64.com/">OOO Program Verification Systems</a>公司用自己的静态源码分析产品PVS-Studio对一些知名的C/C++开源项目，诸如<a href="http://httpd.apache.org">Apache Http Server</a>、<a href="http://www.chromium.org">Chromium</a>、<a href="http://clang.llvm.org">Clang</a>、<a href="http://www.cmake.org">CMake</a>、<a href="http://www.mysql.com">MySQL</a>等的源码进行了分析，找出了<a href="http://www.viva64.com/en/a/0079/">100个典型的Bugs</a>。个人觉得这份列表对C/C++ 程序员有一定参考意义。与其说事后用静态工具分析，倒不如在编码时就提高自知自觉，避免这份列表上的错误发生在你的代码中，因此这里将部分摘录一些Bugs（Bug编号这里不连续，为的是对应原文的编号）并做简要说明。原文将这份Bug列表分为了几类，这里也将沿用这个思路。</p>
<p><b>一、数组和字符串处理错误</b></p>
<p>数组和字符串处理错误是C/C++程序中最多的一类缺陷类型。这也可以看作是我们为拥有高效地底层内存操作能力而付出的代价。</p>
<p><b>[</b><b><tt>#1</tt></b><b>]</b><a href="http://en.wikipedia.org/wiki/Wolfenstein_3D">&nbsp;Wolfenstein 3D</a>项目 -&quot;只有部分对象被clear了&quot;</p>
<p><span style="font-family: 'courier new', courier, monospace;">void CG_RegisterItemVisuals( int itemNum ) {<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; itemInfo_t *itemInfo;<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; memset( itemInfo, 0, sizeof( &amp;itemInfo ) );<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>这里的Bug出现在memset那一行。代码的真实意图是clear iteminfo这块内存，但调用memset时，第三个参数传入的却是sizeof(&amp;iteminfo)，要知道 sizeof(&amp;itemInfo) != sizeof(itemInfo_t)，前者只是一个指针的大小罢了。正确的写法是：</p>
<p><span style="font-family: 'courier new', courier, monospace;">memset(itemInfo, 0, sizeof(itemInfo_t)); 或memset(itemInfo, 0, sizeof(*itemInfo));</span></p>
<p><b>[#2]&nbsp;</b>Wolfenstein 3D项目 -&quot;只有部分Matrix被clear了&quot;</p>
<p><span style="font-family: 'courier new', courier, monospace;">ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {<br />
	&nbsp;&nbsp;&nbsp; memcpy( mat, src, sizeof( src ) );<br />
	}</span></p>
<p>这里的Bug出现在memcpy一行。程序的原意是将clear src[3][3]这个<a href="http://tonybai.com/2013/03/28/pointer-and-multi-dimension-array-in-c/">二维数组</a>。但这里有个坑：那就是作为函数形式参数的数组名已经退化为指针了，对其sizeof只能得到一个指针的长度，因此这里的 memcpy只是copy了一个指针的长度，没有copy全。这里的代码是C++代码，原文中给出了正确的改正方法 &#8211; 传reference：</p>
<p><span style="font-family: 'courier new', courier, monospace;">ID_INLINE mat3_t::mat3_t( float (&amp;src)[3][3] )<br />
	{<br />
	&nbsp;&nbsp;&nbsp; memcpy( mat, src, sizeof( src ) );<br />
	}</span></p>
<p><b>[#4]&nbsp;</b><a href="http://www.reactos.org">ReactOS</a>项目 &#8211; &quot;错误地计算一个字符串的长度&quot;</p>
<p><span style="font-family: 'courier new', courier, monospace;">static const PCHAR Nv11Board = &quot;NV11 (GeForce2) Board&quot;;<br />
	static const PCHAR Nv11Chip = &quot;Chip Rev B2&quot;;<br />
	static const PCHAR Nv11Vendor = &quot;NVidia Corporation&quot;;</span></p>
<p><span style="font-family:courier new,courier,monospace;">BOOLEAN<br />
	IsVesaBiosOk(&#8230;)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; if (!(strncmp(Vendor, Nv11Vendor, sizeof(Nv11Vendor))) &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; !(strncmp(Product, Nv11Board, sizeof(Nv11Board))) &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; !(strncmp(Revision, Nv11Chip, sizeof(Nv11Chip))) &amp;&amp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (OemRevision == 0&#215;311))<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>Bug处在IsVesaBiosOK中那一串strncmp调用中，代码将一个指针的size传入strncmp作为第三个参数，导致 strncmp实际只是比较了字符串的前4 or 8个字节，而不是字符串的全部内容。</p>
<p><b>[#6]&nbsp;</b>CPU Identifying Tool项目 &#8211; 数组越界</p>
<p><span style="font-family: 'courier new', courier, monospace;">#define FINDBUFFLEN 64&nbsp; // Max buffer find/replace size<br />
	&#8230;<br />
	int WINAPI Sticky (&#8230;)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; static char findWhat[FINDBUFFLEN] = {&#39;\0&#39;};<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; findWhat[FINDBUFFLEN] = &#39;\0&#39;;<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>bug出在&quot;findWhat[FINDBUFFLEN] = &#8216;\0&#8242;;”这一行。数组的最大长度为FINDBUFFLEN，但下标的最大值应该是FINDBUFFLEN-1，而不是FINDBUFFLEN。因此这 行代码显然应该改为findWhat[FINDBUFFLEN-1] = &#39;\0&#39;;</p>
<p><b>[#7]</b>&nbsp;Wolfenstein 3D项目 &#8211; 数组越界</p>
<p><span style="font-family: 'courier new', courier, monospace;">typedef struct bot_state_s<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; char teamleader[32]; //netname of the team leader<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}&nbsp; bot_state_t;</span></p>
<p><span style="font-family:courier new,courier,monospace;">void BotTeamAI( bot_state_t *bs ) {<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; bs-&gt;teamleader[sizeof( bs->teamleader )] = &#39;\0&#39;;<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>&quot;sizeof( bs-&gt;teamleader )]&quot;这行的结果值已经超出了数组的最大边界，正确的代码是：</p>
<p><span style="font-family: 'courier new', courier, monospace;">bs-&gt;teamleader[<br />
	&nbsp; sizeof(bs-&gt;teamleader) / sizeof(bs-&gt;teamleader[0]) &#8211; 1<br />
	&nbsp; ] = &#39;\0&#39;;</span></p>
<p><b>[#8]&nbsp;</b><a href="http://www.miranda-im.org">Miranda IM</a>项目 &#8211; 只Copy了部分字符串</p>
<p><span style="font-family: 'courier new', courier, monospace;">struct _textrangew<br />
	{<br />
	&nbsp;&nbsp;&nbsp; CHARRANGE chrg;<br />
	&nbsp;&nbsp;&nbsp; LPWSTR lpstrText;<br />
	} TEXTRANGEW;</span></p>
<p><span style="font-family:courier new,courier,monospace;">const wchar_t* Utils::extractURLFromRichEdit(&#8230;)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; ::CopyMemory(tr.lpstrText, L&quot;mailto:&quot;, 7);<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>这里的bug在于L&quot;mailto:&quot;是宽字符串，宽字符串中的每个字符占2或4个字节（依Compiler使用的<a href="http://tonybai.com/2007/11/03/also-talk-about-char-encoding/">字符集</a>编码而定），因此这里只 copy 7个字节显然是不够的，应该是7 * sizeof(wchar_t)。</p>
<p><b>[#9]</b>&nbsp;CMake项目 &#8211; 循环內的数组越界</p>
<p><span style="font-family: 'courier new', courier, monospace;">static const struct {<br />
	&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; winerr;<br />
	&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp; doserr;<br />
	} doserrors[] =<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	};</span></p>
<p><span style="font-family:courier new,courier,monospace;">static void<br />
	la_dosmaperr(unsigned long e)<br />
	{<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; sizeof(doserrors); i++)<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (doserrors[i].winerr == e)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; errno = doserrors[i].doserr;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</span></p>
<p>作者原本意图la_dosmaperr中for循环的次数等于数组的元素个数，但sizeof(doserrors)返回的却是数组占用的字节个数，这远远大于数组元素个数，因此造成数组越界。正确的写法：</p>
<p><span style="font-family: 'courier new', courier, monospace;">for (i = 0; i &lt; sizeof(doserrors) / sizeof(*doserrors); i++)</span></p>
<p><strong>[#10]</strong>&nbsp;CPU Identifying Tool项目 &#8211; 打印到自身的字符串</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">char * OSDetection ()<br />
	{<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; sprintf(szOperatingSystem,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;%sversion %d.%d %s (Build %d)&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; szOperatingSystem,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; osvi.dwMajorVersion,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; osvi.dwMinorVersion,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; osvi.szCSDVersion,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; osvi.dwBuildNumber &amp; 0xFFFF);<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; sprintf (szOperatingSystem, &quot;%s%s(Build %d)&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; szOperatingSystem, osvi.szCSDVersion,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; osvi.dwBuildNumber &amp; 0xFFFF);<br />
	&nbsp; &nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">通过sprintf，szOperatingSystem字符串将自己打印到自己里面，这是十分危险的，将导致无法预知的错误结果，可能会导致栈溢出等严重问题。</p>
<p style="font-size: 13px;"><strong>[#12]</strong>&nbsp;<a href="http://notepad-plus-plus.org">Notepad++</a>项目 &#8211; 数组局部clear</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">#define CONT_MAP_MAX 50<br />
	int _iContMap[CONT_MAP_MAX];<br />
	&#8230;<br />
	DockingManager::DockingManager()<br />
	{<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; memset(_iContMap, -1, CONT_MAP_MAX);<br />
	&nbsp; &nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">代码的原本试图将数组_iContMap清零，但memset的第三个参数CONT_MAP_MAX并不能代表数组的真正大小，而只是数组的元素个数而已，显然其忘记乘以sizeof(int)了。</p>
<p style="font-size: 13px;"><b>二、未定义行为</b></p>
<p style="font-size: 13px;">在C/C++的语言规范中，我们常常能看到&ldquo;xx is undefined&rdquo;。规范中并没有明确表明这类错误是什么样子的，只是说取决于Compiler的实现，也许Compiler会给出正确的结果，但这么使用却是不可移植的。</p>
<p style="font-size: 13px;"><strong>[#1]</strong>&nbsp;Chromium项目 &#8211; 智能指针的误用</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">void AccessibleContainsAccessible(&#8230;)<br />
	{<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; auto_ptr&lt;VARIANT&gt; child_array(new VARIANT[child_count]);<br />
	&nbsp; &nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这里的问题在于使用new[]分配的内存，在智能指针释放时却用了delete，这将会导致未定义行为。看看autoptr的destructor就知道了：</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">~auto_ptr() {<br />
	&nbsp; &nbsp; delete _Myptr;<br />
	}</span></p>
<p style="font-size: 13px;">我们可以找一些更合适的类来fix这个问题，比如boost::scopedarray。</p>
<p style="font-size: 13px;"><strong>[#2]</strong>&nbsp;IPP Sample项目 &#8211; 经典未定义行为</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">template&lt;typename T, Ipp32s size&gt; void HadamardFwdFast(&#8230;)<br />
	{<br />
	&nbsp; Ipp32s *pTemp;<br />
	&nbsp; &#8230;<br />
	&nbsp; for(j=0;j&lt;4;j++) {<br />
	&nbsp; &nbsp; a[0] = pTemp[0*4] + pTemp[1*4];<br />
	&nbsp; &nbsp; a[1] = pTemp[0*4] &#8211; pTemp[1*4];<br />
	&nbsp; &nbsp; a[2] = pTemp[2*4] + pTemp[3*4];<br />
	&nbsp; &nbsp; a[3] = pTemp[2*4] &#8211; pTemp[3*4];<br />
	&nbsp; &nbsp; pTemp = pTemp++;<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">很多人一眼就看到了&quot;pTemp = pTemp++&quot;这行，对于这个代码编译器会产生两种结果截然不同的翻译：</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">pTemp = pTemp + 1;<br />
	pTemp = pTemp;</span></p>
<p style="font-size: 13px;">或</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">TMP = pTemp;<br />
	pTemp = pTemp + 1;<br />
	pTemp = TMP;</span></p>
<p style="font-size: 13px;">到底是哪种呢？依赖于编译器的实现，甚至是优化级别的设定。</p>
<p style="font-size: 13px;"><b>三、与运算优先级相关的错误</b></p>
<p style="font-size: 13px;"><strong>[#1]</strong>&nbsp;MySQL工程 &#8211; !和&amp;的运算优先级</p>
<p style="font-size: 13px;"><span style="font-family: 'courier new', courier, monospace;">int ha_innobase::create(&#8230;)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (srv_file_per_table<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;&amp; !mysqld_embedded<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;&amp; (!create_info-&gt;options &amp; HA_LEX_CREATE_TMP_TABLE)) {<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这段代码原意是想测试create_info-&gt;options变量中几个bit位的值是否set了，即!(create_info-&gt;options &amp; HA_LEX_CREATE_TMP_TABLE)，但由于!的运算优先级高于&amp;，实际逻辑变成了(!create_info-&gt;options) &amp;&nbsp;HA_LEX_CREATE_TMP_TABLE了。如果想要这段代码如期工作，就不要吝啬小括号了。</p>
<p style="font-size: 13px;"><strong>[#2]</strong>&nbsp;<a href="http://www.emule-project.net">Emule</a>工程 &#8211; *和++的运算优先级</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">STDMETHODIMP<br />
	CCustomAutoComplete::Next(&#8230;, ULONG *pceltFetched)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (pceltFetched != NULL)<br />
	&nbsp; &nbsp; *pceltFetched++;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">显然作者原意是想对pceltFetched所指向的long型变量进行++操作，但由于*和++的运算优先级没有搞对，导致实际上执行了*(pceltFetched++)的操作，而不是(*pceltFetched)++操作。</p>
<p style="font-size: 13px;"><strong>[#3]</strong> Chromium项目 &#8211; &amp;和!=的运算优先级</p>
<p><span style="font-family:courier new,courier,monospace;">#define FILE_ATTRIBUTE_DIRECTORY 0&#215;00000010</span></p>
<p><span style="font-family:courier new,courier,monospace;">bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {<br />
	&nbsp; &#8230;<br />
	&nbsp; info-&gt;is_directory =<br />
	&nbsp; &nbsp; file_info.dwFileAttributes &amp; FILE_ATTRIBUTE_DIRECTORY != 0;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这个程序员的意图是通过测试file_info.dwFileAttributes的几个bit位的值来判定是否是目录，逻辑上应该是(file_info.dwFileAttributes &amp; FILE_ATTRIBUTE_DIRECTORY) != 0，但由于!=优先级高于&amp;，原代码中无括号，结果逻辑变成了file_info.dwFileAttributes &amp; (FILE_ATTRIBUTE_DIRECTORY != 0)，导致is_directory将永远求值为true。</p>
<p style="font-size: 13px;"><strong>[#4]</strong> BCmenu项目 &#8211; if和else弄混</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void BCMenu::InsertSpaces(void)<br />
	{<br />
	&nbsp; if(IsLunaMenuStyle())<br />
	&nbsp; &nbsp; if(!xp_space_accelerators) return;<br />
	&nbsp; else<br />
	&nbsp; &nbsp; if(!original_space_accelerators) return;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这又是C语言的一个&ldquo;大坑&rdquo;，无奈这个BCMenu项目的程序员掉坑里了。虽然从代码缩进上来看，else似乎是与最外层的if配对使用，但实际这段代码的效果是：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">if(IsLunaMenuStyle())<br />
	{<br />
	&nbsp; &nbsp;if(!xp_space_accelerators) {<br />
	&nbsp; &nbsp; &nbsp;return;<br />
	&nbsp; &nbsp;} else {<br />
	&nbsp; &nbsp; &nbsp;if(!original_space_accelerators) return;<br />
	&nbsp; &nbsp;}<br />
	}</span></p>
<p style="font-size: 13px;">这显然不是程序员原意，看来括号必要时还是不能省略的。修改后的代码如下：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">if(IsLunaMenuStyle()) {<br />
	&nbsp; if(!xp_space_accelerators) return;<br />
	} else {<br />
	&nbsp; if(!original_space_accelerators) return;<br />
	}</span></p>
<p style="font-size: 13px;"><b style="font-size: 13px;">四、格式化输出错误</b></p>
<p style="font-size: 13px;"><strong>[#1]</strong> ReactOS项目 &#8211; 错误地输出WCHAR字符</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">static void REGPROC_unescape_string(WCHAR* str)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; default:<br />
	&nbsp; &nbsp; fprintf(stderr,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;Warning! Unrecognized escape sequence: \\%c&#39;\n&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; str[str_idx]);<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">%c是用来格式化输出非宽字符的，这里用来输出WCHAR显然会得到错误的结果，fix solution是将%c换位%C。</p>
<p style="font-size: 13px;"><strong>[#2]</strong> Intel AMT SDK项目 &#8211; 缺少%s</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void addAttribute(&#8230;)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; int index = _snprintf(temp, 1023,&nbsp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;%02x%02x:%02x%02x:%02x%02x:%02x%02x:&quot;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;%02x%02x:02x%02x:%02x%02x:%02x%02x&quot;,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value[0],value[1],value[2],value[3],value[4],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value[5],value[6],value[7],value[8],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value[9],value[10],value[11],value[12],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value[13],value[14],value[15]);<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">&nbsp;</p>
<p style="font-size: 13px;">不解释了，自己慢慢数和对照吧。</p>
<p style="font-size: 13px;"><strong>[#3]</strong>&nbsp;Intel AMT SDK项目 &#8211; 未使用的参数</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">bool GetUserValues(&#8230;)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; printf(&quot;Error: illegal value. Aborting.\n&quot;, tmp);<br />
	&nbsp; return false;<br />
	}</span></p>
<p style="font-size: 13px;">显然tmp是多余的。</p>
<p style="font-size: 13px;"><b style="font-size: 13px;">五、书写错误</b></p>
<p style="font-size: 13px;"><strong>[#1]</strong> Miranda IM项目 &#8211; 在if中赋值</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; else if (wTLVType = 0&#215;29 &amp;&amp; wTLVLen == sizeof(DWORD))<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">&ldquo;wTLVType = 0&#215;29&rdquo;显然是笔误，应该是&ldquo;wTLVType ==&nbsp;0&#215;29&rdquo;才对。</p>
<p style="font-size: 13px;"><strong>[#3]</strong> Clang项目 &#8211; 对象名书写错误</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">static Value *SimplifyICmpInst(&#8230;) {<br />
	&nbsp; &#8230;<br />
	&nbsp; case Instruction::Shl: {<br />
	&nbsp; &nbsp; bool NUW =<br />
	&nbsp; &nbsp; &nbsp; LBO-&gt;hasNoUnsignedWrap() &amp;&amp; LBO-&gt;hasNoUnsignedWrap();<br />
	&nbsp; &nbsp; bool NSW =<br />
	&nbsp; &nbsp; &nbsp; LBO-&gt;hasNoSignedWrap() &amp;&amp; RBO-&gt;hasNoSignedWrap();<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">从最后一行先后使用了LBO和RBO来看，前面只用了LBO的那行很可能是有问题的，正确的应该是：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">bool NUW =<br />
	&nbsp; &nbsp; &nbsp; LBO-&gt;hasNoUnsignedWrap() &amp;&amp; RBO-&gt;hasNoUnsignedWrap();</span></p>
<p style="font-size: 13px;"><strong>[#6]</strong> G3D Content Pak项目 &#8211; 一对括号放错了地方</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">bool Matrix4::operator==(const Matrix4&amp; other) const {<br />
	&nbsp; if (memcmp(this, &amp;other, sizeof(Matrix4) == 0)) {<br />
	&nbsp; &nbsp; return true;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">由于括号放错了地方，导致memcmp最后的参数变成了sizeof(Matrix4) == 0，这行代码的正确写法应该是：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">if (memcmp(this, &amp;other, sizeof(Matrix4)) == 0) {</span></p>
<p style="font-size: 13px;"><strong>[#8]</strong> Apache Http Server项目 &#8211; 多余的sizeof</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">PSECURITY_ATTRIBUTES GetNullACL(void)<br />
	{<br />
	&nbsp; PSECURITY_ATTRIBUTES sa;<br />
	&nbsp; sa &nbsp;= (PSECURITY_ATTRIBUTES)<br />
	&nbsp; &nbsp; LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));<br />
	&nbsp; sa-&gt;nLength = sizeof(sizeof(SECURITY_ATTRIBUTES));<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">最后一行显然是笔误，sizeof(sizeof(SECURITY_ATTRIBUTES))应该写为sizeof(SECURITY_ATTRIBUTES)才对。</p>
<p style="font-size: 13px;"><strong>[#10]</strong> Notepad++项目 &#8211; 在本来应该用&amp;的地方使用了&amp;&amp;</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">TCHAR GetASCII(WPARAM wParam, LPARAM lParam)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; result=ToAscii(wParam,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(lParam &gt;&gt; 16) &amp;&amp; 0xff, keys,&amp;dwReturnedValue,0);<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">(lParam &gt;&gt; 16) &amp;&amp; 0xff没有什么意义，求值结果总是true。这里的代码应该是(lParam &gt;&gt; 16) &amp;&nbsp;0xff。</p>
<p style="font-size: 13px;"><strong>[#12]</strong>&nbsp;Fennec Media Project项目 &#8211; 额外的分号</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">int settings_default(void)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; for(i=0; i&lt;16; i++);<br />
	&nbsp; &nbsp; for(j=0; j&lt;32; j++)<br />
	&nbsp; &nbsp; {<br />
	&nbsp; &nbsp; &nbsp; settings.conversion.equalizer_bands.boost[i][j] = 0.0;<br />
	&nbsp; &nbsp; &nbsp; settings.conversion.equalizer_bands.preamp[i] &nbsp; = 0.0;<br />
	&nbsp; &nbsp; }<br />
	}</span></p>
<p style="font-size: 13px;">这又是一个实际逻辑与代码缩进不符的例子。作者的原意是这样的：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">for(i=0; i&lt;16; i++)&nbsp;<br />
	{<br />
	&nbsp; &nbsp; for(j=0; j&lt;32; j++)<br />
	&nbsp; &nbsp; {<br />
	&nbsp; &nbsp; &nbsp; settings.conversion.equalizer_bands.boost[i][j] = 0.0;<br />
	&nbsp; &nbsp; &nbsp; settings.conversion.equalizer_bands.preamp[i] &nbsp; = 0.0;<br />
	&nbsp; &nbsp; }<br />
	}</span></p>
<p style="font-size: 13px;">但实际执行代码逻辑却是：</p>
<p><span style="font-family:courier new,courier,monospace;">for(i=0; i&lt;16; i++)&nbsp;<br />
	{<br />
	&nbsp; &nbsp; ;<br />
	}</span></p>
<p><span style="font-family:courier new,courier,monospace;">for(j=0; j&lt;32; j++)<br />
	{ &nbsp;&nbsp;<br />
	&nbsp; settings.conversion.equalizer_bands.boost[i][j] = 0.0;<br />
	&nbsp; settings.conversion.equalizer_bands.preamp[i] &nbsp; = 0.0;<br />
	}</span></p>
<p style="font-size: 13px;">这一切都是那个;导致的。</p>
<p style="font-size: 13px;"><strong>六、对基本函数和类的误用</strong></p>
<p style="font-size: 13px;"><strong>[#2]</strong>&nbsp;TortoiseSVN项目 &#8211; remove函数的误用</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">STDMETHODIMP CShellExt::Initialize(&#8230;.)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; ignoredprops = UTF8ToWide(st.c_str());<br />
	&nbsp; // remove all escape chars (&#39;\\&#39;)<br />
	&nbsp; std::remove(ignoredprops.begin(), ignoredprops.end(), &#39;\\&#39;);<br />
	&nbsp; break;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">作者意图删除所有&#39;\\&#39;，但他用错了函数，remove函数只是交换元素的位置，将要删除的元素交换到尾部trash，并且返回指向trash首地址的iterator。正确的做法应该是&quot;v.erase(remove(v.begin(), v.end(), 2), v.end())&quot;。</p>
<p style="font-size: 13px;"><strong>[#5]</strong> Pixie项目 &#8211; 在循环中使用alloca函数</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">inline &nbsp;void &nbsp;triangulatePolygon(&#8230;) {<br />
	&nbsp; &#8230;<br />
	&nbsp; for (i=1;i&lt;nloops;i++) {<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; do {<br />
	&nbsp; &nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; &nbsp; do {<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; CTriVertex &nbsp;*snVertex =<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(CTriVertex *)alloca(2*sizeof(CTriVertex));<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; &nbsp; } while(dVertex != loops[0]);<br />
	&nbsp; &nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; } while(sVertex != loops[i]);<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">alloca函数在栈上分配内存，因此在循环中使用alloca可能会很快导致栈溢出。</p>
<p style="font-size: 13px;"><strong style="font-size: 13px;">七、无意义的代码</strong></p>
<p style="font-size: 13px;"><strong>[#1]</strong> IPP Samples项目 &#8211; 不完整的条件</p>
<p><span style="font-family:courier new,courier,monospace;">void lNormalizeVector_32f_P3IM(Ipp32f *vec[3],<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Ipp32s* mask, Ipp32s len)<br />
	{<br />
	&nbsp; Ipp32s &nbsp;i;<br />
	&nbsp; Ipp32f &nbsp;norm;</span></p>
<p><span style="font-family:courier new,courier,monospace;">&nbsp; for(i=0; i&lt;len; i++) {<br />
	&nbsp; &nbsp; if(mask&lt;0) continue;<br />
	&nbsp; &nbsp; norm = 1.0f/sqrt(vec[0][i]*vec[0][i]+<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;vec[1][i]*vec[1][i]+vec[2][i]*vec[2][i]);<br />
	&nbsp; &nbsp; vec[0][i] *= norm; vec[1][i] *= norm; vec[2][i] *= norm;<br />
	&nbsp; }<br />
	}</span></p>
<p style="font-size: 13px;">mask是Ipp32s类型指针，这样if (mask&lt; 0)这句代码显然没啥意义，正确的代码应该是：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">if (mask[i] &lt; 0) continue;</span></p>
<p style="font-size: 13px;"><strong>[#2]</strong> QT项目 &#8211; 重复的检查</p>
<p><span style="font-family:courier new,courier,monospace;">Q3TextCustomItem* Q3TextDocument::parseTable(&#8230;)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; while (end &lt; length<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;/td&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;td&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;/th&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;th&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;td&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;/tr&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;tr&quot;))<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;&amp; !hasPrefix(doc, length, end, QLatin1String(&quot;&lt;/table&quot;))) {</span></p>
<p><span style="font-family:courier new,courier,monospace;">&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这里对&quot;&lt;td&quot;做了两次check。</p>
<p style="font-size: 13px;"><strong style="font-size: 13px;">八、总是True或False的条件</strong></p>
<p style="font-size: 13px;"><strong>[#1]</strong>&nbsp;Shareaza项目 &#8211; char类型的值范围</p>
<p><span style="font-family:courier new,courier,monospace;">void CRemote::Output(LPCTSTR pszName)<br />
	{</span></p>
<p><span style="font-family:courier new,courier,monospace;">&nbsp; &#8230;<br />
	&nbsp; CHAR* pBytes = new CHAR[ nBytes ];<br />
	&nbsp; hFile.Read( pBytes, nBytes );<br />
	&nbsp; &#8230;<br />
	&nbsp; if ( nBytes &gt; 3 &amp;&amp; pBytes[0] == 0xEF &amp;&amp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pBytes[1] == 0xBB &amp;&amp; pBytes[2] == 0xBF )<br />
	&nbsp; {<br />
	&nbsp; &nbsp; pBytes += 3;<br />
	&nbsp; &nbsp; nBytes -= 3;<br />
	&nbsp; &nbsp; bBOM = true;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p>表达式&quot;pBytes[0] == 0xEF&quot;总是False。char类型的值范围是-128~127 &lt; 0xEF，因此这个表达式总是False，导致整个if condition总是为False，与预期逻辑不符。</p>
<p><strong>[#3]</strong> VirtualDub项目 &#8211; 无符号类型总是&gt;=0</p>
<p><span style="font-family:courier new,courier,monospace;">typedef unsigned short wint_t;<br />
	&#8230;<br />
	void lexungetc(wint_t c) {<br />
	&nbsp; if (c &lt; 0)<br />
	&nbsp; &nbsp; return;<br />
	&nbsp; &nbsp;g_backstack.push_back(c);<br />
	}</span></p>
<p>c是unsigned short类型，永远不会小于0,也就是说if (c &lt; 0)永远为False。</p>
<p><strong>[#8]</strong> MySQL项目 &#8211; 条件错误</p>
<p><span style="font-family:courier new,courier,monospace;">enum enum_mysql_timestamp_type<br />
	str_to_datetime(&#8230;)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; else if (str[0] != &#8216;a&#8217; || str[0] != &#39;A&#39;)<br />
	&nbsp; &nbsp; continue; /* Not AM/PM */<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p>if (str[0] != &#8216;a&#8217; || str[0] != &#39;A&#39;)这个条件永远为真。也许这块本意是想用&amp;&amp;。</p>
<p><strong style="font-size: 13px;">九、代码漏洞</strong></p>
<p>导致漏洞的代码错误实际上也都是笔误、不正确的条件以及不正确的数组操作等。但这里还是想将一些特定错误划归为一类，因为入侵者可以利用这些错误来攻击你的代码，获取其利益。</p>
<p><strong>[#1]</strong> Ultimate TCP/IP项目 &#8211; 空字符串的错误检查</p>
<p><span style="font-family:courier new,courier,monospace;">char *CUT_CramMd5::GetClientResponse(LPCSTR ServerChallenge)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (m_szPassword != NULL)<br />
	&nbsp; {<br />
	&nbsp; &nbsp; &#8230;<br />
	&nbsp; &nbsp; if (m_szPassword != &#39;\0&#39;)<br />
	&nbsp; &nbsp; {<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p>第二个if condition check意图检查m_szPassword是否为空字符串，但却错误的将指针与&#39;\0&#39;进行比较，正确的代码应该是这样的：</p>
<p><span style="font-family:courier new,courier,monospace;">if (*m_szPassword != &#39;\0&#39;)</span></p>
<p style="font-size: 13px;"><strong>[#2]</strong> Chromium项目 &#8211; NULL指针的处理</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">bool ChromeFrameNPAPI::Invoke(&#8230;)<br />
	{<br />
	&nbsp; ChromeFrameNPAPI* plugin_instance =<br />
	&nbsp; &nbsp; ChromeFrameInstanceFromNPObject(header);<br />
	&nbsp; if (!plugin_instance &amp;&amp;<br />
	&nbsp; &nbsp; &nbsp; (plugin_instance-&gt;automation_client_.get()))<br />
	&nbsp; &nbsp; return false;<br />
	&nbsp; &#8230;<br />
	} &nbsp;&nbsp;</span></p>
<p style="font-size: 13px;">一旦plugin_instance为NULL，!plugin_instance为True，代码对&amp;&amp;后面的子条件求值，引用plugin_instance将导致程序崩溃。正确的做法应该是：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">if (plugin_instance &amp;&amp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; (plugin_instance-&gt;automation_client_.get()))<br />
	&nbsp; return false;</span></p>
<p style="font-size: 13px;"><strong>[#5]</strong> Apache httpd Server项目 &#8211; 不完整的缓冲区clear</p>
<p><span style="font-family:courier new,courier,monospace;">#define MEMSET_BZERO(p,l) &nbsp; &nbsp; &nbsp; memset((p), 0, (l))</span></p>
<p><span style="font-family:courier new,courier,monospace;">void apr__SHA256_Final(&#8230;, SHA256_CTX* context) {<br />
	&nbsp; &#8230;<br />
	&nbsp; MEMSET_BZERO(context, sizeof(context));<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">这个错误前面提到过，sizeof(context)只是指针的大小，将之改为sizeof(*context)就OK了。</p>
<p style="font-size: 13px;"><strong>[#7]</strong> PNG Library项目 &#8211; 意外的指针clear</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">png_size_t<br />
	png_check_keyword(png_structp png_ptr, png_charp key,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; png_charpp new_key)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (key_len &gt; 79)<br />
	&nbsp; {<br />
	&nbsp; &nbsp; png_warning(png_ptr, &quot;keyword length must be 1 &#8211; 79 characters&quot;);<br />
	&nbsp; &nbsp; new_key[79] = &#39;\0&#39;;<br />
	&nbsp; &nbsp; key_len = 79;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">new_key的类型为png_charpp，顾名思义，这是一个char**类型，但代码中new_key[79] = &#8216;\0&#8242;这句显然是要给某个char赋值，但new_key[n]得到的应该是一个地址，给一个地址赋值为&#8217;\0&#8242;显然是有误的。正确的写法应该是(*new_key)[79] = &#39;\0&#39;。</p>
<p style="font-size: 13px;"><strong>[#10]</strong> Miranda IM项目 &#8211; 保护没生效</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void Append( PCXSTR pszSrc, int nLength )<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; UINT nOldLength = GetLength();<br />
	&nbsp; if (nOldLength &lt; 0)<br />
	&nbsp; {<br />
	&nbsp; &nbsp; // protects from underflow<br />
	&nbsp; &nbsp; nOldLength = 0;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">nOldLength椒UINT类型，其值永远不会小于0,因此if (nOldLength &lt; 0)这行成了摆设。</p>
<p style="font-size: 13px;"><strong>[#12]</strong> Ultimate TCP/IP项目 &#8211; 不正确的循环结束条件</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void CUT_StrMethods::RemoveSpaces(LPSTR szString) {<br />
	&nbsp; &#8230;<br />
	&nbsp; size_t loop, len = strlen(szString);<br />
	&nbsp; // Remove the trailing spaces<br />
	&nbsp; for(loop = (len-1); loop &gt;= 0; loop&#8211;) {<br />
	&nbsp; &nbsp; if(szString[loop] != &#39; &#39;)<br />
	&nbsp; &nbsp; &nbsp; break;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">循环中的结束条件loop &gt;= 0将永远为True，因为loop变量的类型是size_t是unsigned类型，永远不会小于0。</p>
<p style="font-size: 13px;"><strong style="font-size: 13px;">十、拷贝粘贴</strong></p>
<p style="font-size: 13px;">和笔误不同，程序员们决不因该低估拷贝粘贴问题，这类问题发生了太多。程序员们花费了大量时间在这些问题的debug上。</p>
<p style="font-size: 13px;"><strong>[#1]</strong> Fennec Media Project项目 &#8211; 处理数组元素时出错</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void* tag_write_setframe(char *tmem,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;const char *tid, const string dstr)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if(lset)<br />
	&nbsp; {<br />
	&nbsp; &nbsp; fhead[11] = &#39;\0&#39;;<br />
	&nbsp; &nbsp; fhead[12] = &#39;\0&#39;;<br />
	&nbsp; &nbsp; fhead[13] = &#39;\0&#39;;<br />
	&nbsp; &nbsp; fhead[13] = &#39;\0&#39;;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">&nbsp;</p>
<p style="font-size: 13px;">咋看一下，fhead[13]做了两次赋值，似乎没啥问题。但仔细想一下，最后那行程序员的原意极可能是想写fhead[14] = &#39;\0&#39;。问题就在这里了。</p>
<p style="font-size: 13px;">[#2] MySQL项目 &#8211; 处理数组元素时出错</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">static int rr_cmp(uchar *a,uchar *b)<br />
	{<br />
	&nbsp; if (a[0] != b[0])<br />
	&nbsp; &nbsp; return (int) a[0] &#8211; (int) b[0];<br />
	&nbsp; if (a[1] != b[1])<br />
	&nbsp; &nbsp; return (int) a[1] &#8211; (int) b[1];<br />
	&nbsp; if (a[2] != b[2])<br />
	&nbsp; &nbsp; return (int) a[2] &#8211; (int) b[2];<br />
	&nbsp; if (a[3] != b[3])<br />
	&nbsp; &nbsp; return (int) a[3] &#8211; (int) b[3];<br />
	&nbsp; if (a[4] != b[4])<br />
	&nbsp; &nbsp; return (int) a[4] &#8211; (int) b[4];<br />
	&nbsp; if (a[5] != b[5])<br />
	&nbsp; &nbsp; return (int) a[1] &#8211; (int) b[5];<br />
	&nbsp; if (a[6] != b[6])<br />
	&nbsp; &nbsp; return (int) a[6] &#8211; (int) b[6];<br />
	&nbsp; return (int) a[7] &#8211; (int) b[7];<br />
	}</span></p>
<p style="font-size: 13px;">&nbsp;</p>
<p style="font-size: 13px;">编写这类代码时，我猜绝大多数人会选择Copy-Paste，然后再逐行修改，问题就发生在修改过程中，上面的代码中当处理a[5] != b[5]时就忘记修改一个下标了：return (int) a[1] &#8211; (int) b[5];显然这里的正确代码应该是return (int) a[5] &#8211; (int) b[5]。</p>
<p style="font-size: 13px;"><strong>[#3]</strong>&nbsp;TortoiseSVN项目 文件名不正确</p>
<p><span style="font-family:courier new,courier,monospace;">BOOL GetImageHlpVersion(DWORD &amp;dwMS, DWORD &amp;dwLS)<br />
	{<br />
	&nbsp; return(GetInMemoryFileVersion((&quot;DBGHELP.DLL&quot;),<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dwMS, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dwLS)) ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br />
	}</span></p>
<p><span style="font-family:courier new,courier,monospace;">BOOL GetDbgHelpVersion(DWORD &amp;dwMS, DWORD &amp;dwLS)<br />
	{<br />
	&nbsp; return(GetInMemoryFileVersion((&quot;DBGHELP.DLL&quot;),<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dwMS, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dwLS)) ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br />
	}</span></p>
<p style="font-size: 13px;">GetImageHlpVersion和GetDbgHelpVersion都使用了&quot;DBGHELP.DLL&quot;文件，显然GetImageHlpVersion写错文件名了。应该用&quot;IMAGEHLP.DLL&quot;就对了。</p>
<p style="font-size: 13px;"><strong>[#4]</strong> Clang项目 &#8211; 等同的函数体</p>
<p><span style="font-family:courier new,courier,monospace;">MapTy PerPtrTopDown;<br />
	MapTy PerPtrBottomUp;</span></p>
<p><span style="font-family:courier new,courier,monospace;">void clearBottomUpPointers() {<br />
	&nbsp; PerPtrTopDown.clear();<br />
	}</span></p>
<p><span style="font-family:courier new,courier,monospace;">void clearTopDownPointers() {<br />
	&nbsp; PerPtrTopDown.clear();<br />
	}</span></p>
<p style="font-size: 13px;">我们看到虽然两个函数名不同，但是函数体的内容是相同的，显然又是copy-paste惹的祸。做如下修改即可：</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void clearBottomUpPointers() {<br />
	&nbsp; PerPtrBottomUp.clear();<br />
	}</span></p>
<p style="font-size: 13px;">&nbsp;</p>
<p style="font-size: 13px;"><strong style="font-size: 13px;">十一、Null指针的校验迟了</strong></p>
<p style="font-size: 13px;">这里的&ldquo;迟了&rdquo;的含义是先使用指针，然后再校验指针是否为NULL。</p>
<p style="font-size: 13px;"><strong>[#1]</strong>&nbsp;Quake-III-Arena项目 &#8211; 校验迟了</p>
<p style="font-size: 13px;"><span style="font-family:courier new,courier,monospace;">void Item_Paint(itemDef_t *item) {<br />
	&nbsp; vec4_t red;<br />
	&nbsp; menuDef_t *parent = (menuDef_t*)item-&gt;parent;<br />
	&nbsp; red[0] = red[3] = 1;<br />
	&nbsp; red[1] = red[2] = 0;<br />
	&nbsp; if (item == NULL) {<br />
	&nbsp; &nbsp; return;<br />
	&nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style="font-size: 13px;">&nbsp;</p>
<p style="font-size: 13px;">在校验item是否为NULL前已经使用过item了，一旦item真的为NULL，那程序必然崩溃。</p>
<p style="font-size: 13px;"><strong>十二、其他杂项</strong></p>
<p style="font-size: 13px;"><strong>[#1]</strong> Image Processing 项目 &#8211; 八进制数</p>
<p><span style="font-family:courier new,courier,monospace;">inline<br />
	void elxLuminocity(const PixelRGBus&amp; iPixel,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;LuminanceCell&lt; PixelRGBus &gt;&amp; oCell)<br />
	{<br />
	&nbsp; oCell._luminance = uint16(0.2220f*iPixel._red +<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0.7067f*iPixel._blue + 0.0713f*iPixel._green);<br />
	&nbsp; oCell._pixel = iPixel;<br />
	}</span></p>
<p><span style="font-family:courier new,courier,monospace;">inline<br />
	void elxLuminocity(const PixelRGBi&amp; iPixel,<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;LuminanceCell&lt; PixelRGBi &gt;&amp; oCell)<br />
	{<br />
	&nbsp; oCell._luminance = 2220*iPixel._red +<br />
	&nbsp; &nbsp; 7067*iPixel._blue + 0713*iPixel._green;<br />
	&nbsp; oCell._pixel = iPixel;<br />
	}</span></p>
<p>第二个函数，程序员原意是使用713这个十进制整数，但0713 != 713，在C中，0713是八进制的表示法，Compiler会认为这是个八进制数。</p>
<p><strong>[#2]</strong> IPP Sample工程 &#8211; 一个变量用于两个loop中</p>
<p><span style="font-family:courier new,courier,monospace;">JERRCODE CJPEGDecoder::DecodeScanBaselineNI(void)<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; for(c = 0; c &lt; m_scan_ncomps; c++)<br />
	&nbsp; {<br />
	&nbsp; &nbsp; block = m_block_buffer + (DCTSIZE2*m_nblock*(j+(i*m_numxMCU)));</span></p>
<p><span style="font-family:courier new,courier,monospace;">&nbsp; &nbsp; // skip any relevant components<br />
	&nbsp; &nbsp; for(c = 0; c &lt; m_ccomp[m_curr_comp_no].m_comp_no; c++)<br />
	&nbsp; &nbsp; {<br />
	&nbsp; &nbsp; &nbsp; block += (DCTSIZE2*m_ccomp[c][/c][/c].m_nblocks);<br />
	&nbsp; &nbsp; }<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p>变量c用在了两个loop中，这会导致只有部分数据被处理，或外部循环中止。</p>
<p><strong>[#3]</strong> Notepad++项目 &#8211; 怪异的条件表达式</p>
<p><span style="font-family:courier new,courier,monospace;">int Notepad_plus::getHtmlXmlEncoding(&#8230;.) const<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (langT != L_XML &amp;&amp; langT != L_HTML &amp;&amp; langT == L_PHP)<br />
	&nbsp; &nbsp; return -1;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p>代码中的那行if条件等价于 if (langT == L_PHP)，显然似乎不是作者原意，猜测正确的代码应该是这样的：</p>
<p><span style="font-family:courier new,courier,monospace;">int Notepad_plus::getHtmlXmlEncoding(&#8230;.) const<br />
	{<br />
	&nbsp; &#8230;<br />
	&nbsp; if (langT != L_XML &amp;&amp; langT != L_HTML &amp;&amp; langT != L_PHP)<br />
	&nbsp; &nbsp; return -1;<br />
	&nbsp; &#8230;<br />
	}</span></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/04/10/100-bugs-in-c-cpp-opensource-projects/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>
