<?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>Trifle on 海市如幻，雕虫求真</title><link>https://victorhge.github.io/zh/categories/trifle/</link><description>Recent content in Trifle on 海市如幻，雕虫求真</description><generator>Hugo -- 0.156.0</generator><language>zh</language><copyright>Source code here, licensed under GPLv3 ·</copyright><lastBuildDate>Thu, 12 Mar 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://victorhge.github.io/zh/categories/trifle/index.xml" rel="self" type="application/rss+xml"/><item><title>X Macros</title><link>https://victorhge.github.io/zh/posts/xmacro/</link><pubDate>Thu, 12 Mar 2026 00:00:00 +0800</pubDate><guid>https://victorhge.github.io/zh/posts/xmacro/</guid><description>C/C++ 项目中解决一致性问题的一种简单技巧</description><content:encoded><![CDATA[<blockquote>
<p>我们无法用制造问题的同一思维层次来解决问题。<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>
&mdash; “阿尔伯特·爱因斯坦”</p>
</blockquote>
<h2 id="一致性挑战">一致性挑战</h2>
<p>我第一次接触 X Macros，是在排查一个越界访问问题的时候。</p>
<p>下面是一段简化后的示例代码：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">// a.h
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">enum</span> {
</span></span><span style="display:flex;"><span>    SYS_OK,
</span></span><span style="display:flex;"><span>    SYS_ERR_TIMEOUT,
</span></span><span style="display:flex;"><span>    SYS_ERR_BUSY,
</span></span><span style="display:flex;"><span>    SYS_ERR_INVALID_ARG,
</span></span><span style="display:flex;"><span>    SYS_ERR_NOT_FOUND
</span></span><span style="display:flex;"><span>} SysState;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// a.c
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>state_desc[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    [SYS_OK] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;System OK&#34;</span>,
</span></span><span style="display:flex;"><span>    [SYS_ERR_TIMEOUT] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Timeout&#34;</span>,
</span></span><span style="display:flex;"><span>    [SYS_ERR_BUSY] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;System Busy&#34;</span>,
</span></span><span style="display:flex;"><span>    [SYS_ERR_INVALID_ARG] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Invalid Argument&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// 这里没有 SYS_ERR_NOT_FOUND
</span></span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">int</span> <span style="color:#a6e22e">main</span>()
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">printf</span>(<span style="color:#e6db74">&#34;State = %s</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, state_desc[SYS_ERR_NOT_FOUND]);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>问题显而易见：枚举中新增了<code>SYS_ERR_NOT_FOUND</code>，但字符串数组没有同步更新。</p>
<p>程序执行到 <code>state_desc[SYS_ERR_NOT_FOUND]</code>时会越过数组边界，最终导致崩溃。</p>
<p>只需要在字符串数组里加一行就可以防止访问越界。但是这样的修复并不能防止同类的问题再次发生。原因在于，这里实际上维护了<strong>两份必须保持一致的数据</strong>：</p>
<ul>
<li>一份是枚举定义</li>
<li>一份是字符串表</li>
</ul>
<p>只要这两份数据分散在不同位置，并且依赖人工同步维护，就很容易再次出现类似问题。</p>
<p>这里需要一种能够防止同类问题再次发生的方案。</p>
<h2 id="x-macros">X Macros</h2>
<p>在查找解决方案的过程中，我看到了 Randy Meyers 发表在 <em>Dr. Dobb&rsquo;s Journal</em> 上的一篇文章 <strong>The New C: X Macros</strong> <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。文章介绍了一种利用 C 预处理器实现简单代码生成的技巧。</p>
<p>这项技巧通常被称为 <strong>X Macros</strong> 。</p>
<p>它的基本思路是维护一份统一的数据列表，然后通过不同的宏定义方式生成所需要的代码结构。</p>
<p>我们可以对前面的例子做一个简单改造, 来展示 X Macros 的用法。</p>
<p>首先定义<strong>单一数据源</strong>：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#define SYS_STATE_LIST                         \
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    X(SYS_OK,              &#34;System OK&#34;)        \
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    X(SYS_ERR_TIMEOUT,     &#34;Timeout&#34;)          \
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    X(SYS_ERR_BUSY,        &#34;System Busy&#34;)      \
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    X(SYS_ERR_INVALID_ARG, &#34;Invalid Argument&#34;) \
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">    X(SYS_ERR_NOT_FOUND,   &#34;Not Found&#34;)
</span></span></span></code></pre></div><p>这里创建了一个包含所有信息的“主列表”，每个条目都包裹在一个占位宏 <code>X()</code> 中 。<code>X()</code>就是这项技巧名称的由来，并没有什么特殊含义，当然也可以用其他名字。</p>
<p>接下来，我们定义宏 <code>X</code> ，利用这份数据生成枚举类型：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#define X(name, desc) name,
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">typedef</span> <span style="color:#66d9ef">enum</span> {
</span></span><span style="display:flex;"><span>    SYS_STATE_LIST
</span></span><span style="display:flex;"><span>} SysState;
</span></span><span style="display:flex;"><span><span style="color:#75715e">#undef X
</span></span></span></code></pre></div><p>然后，重新定义宏 <code>X</code> ，让同一份数据展开成字符串描述数组：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-C" data-lang="C"><span style="display:flex;"><span><span style="color:#75715e">#define X(name, desc) [name] = desc,
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">char</span> <span style="color:#f92672">*</span>state_desc[] <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    SYS_STATE_LIST
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span><span style="color:#75715e">#undef X
</span></span></span></code></pre></div><p>对于熟悉 Python 或 Java 等动态语言的开发者，上述例子可能让您联想到反射机制。可以看到，X Macros 把分散的信息收拢到单一的数据表中，通过对宏 <code>X</code> 的多次定义和撤销
(<code>#undef</code>)，我们可以将上述数据“注入”到不同的模板中 。不论你添加还是删除数据，
甚至前后移动都不会带来不一致的问题。</p>
<p>这项技巧适用于许多需要维护数据一致性的场合：</p>
<ul>
<li>错误码，状态，事件类型及描述的管理</li>
<li>命令，协议定义，解析代码的生成</li>
<li>序列化与反序列化</li>
<li>配置数据初始化与读写代码的生成</li>
</ul>
<h2 id="进阶提示">进阶提示</h2>
<p>X Macros 带来的好处并不是没有代价的。宏的写法会略微降低代码的直观可读性，可调试性。</p>
<ul>
<li>
<p><strong>提高可读性</strong>：可以参考 Andrew Lucas 的建议，把宏 <code>X</code> 作为参数传递给数据列表，以提高代码的可读性<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>。</p>
</li>
<li>
<p><strong>大数据量</strong>：如果数据量足够大，可能会碰到编译器行长度限制。 Randy Meyers在文章中已经给出了方案 —— 把数据列表定义在一个独立的 .def 或者 .h 文件中, 在需要展开的地方用 <code>#include</code> 包含进来。</p>
</li>
<li>
<p><strong>预编译检查</strong>：可以通过 gcc -E 查看预编译结果。如果你用 IDE 结合语言服务器的话，
就更容易了。</p>
</li>
<li>
<p><strong>团队沟通</strong>：这项技巧并非人人皆知，最好在代码中加上必要注释，并向团队成员说明其用途。</p>
</li>
<li>
<p><strong>适度使用</strong>：与其他的宏技巧一样，应当谨慎使用，避免滥用。</p>
</li>
</ul>
<h2 id="总结">总结</h2>
<p>X Macros 本质上是一种基于 C/C++ 预处理器的简单元编程技巧。在缺乏反射或代码生成机制的情况下，这种方法为 C/C++ 提供了一种轻量而实用的工程技巧。</p>
<p>它通过维护一份<strong>单一数据源（Single Source of Truth）</strong>，再利用宏展开生成不同的代码结构，从而避免多处重复定义带来的同步问题。</p>
<p>它的历史可以溯源到上世纪60年代的汇编语言编程，传承至今， 在需要维护大量一致性数据的场景中，它仍然有不可替代的价值, 是每一个C/C++工程师的必备工具。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>溯源的时候，发现这个：<a href="https://www.quora.com/Einstein-said-that-you-cannot-solve-a-problem-from-the-same-level-of-consciousness-that-created-it-What-did-he-mean-with-it-Can-you-use-a-concrete-example">https://www.quora.com/Einstein-said-that-you-cannot-solve-a-problem-from-the-same-level-of-consciousness-that-created-it-What-did-he-mean-with-it-Can-you-use-a-concrete-example</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Randy Meyers. &ldquo;The New C: X macros&rdquo;, Dr.Dobb&rsquo;s 2001, 可访问链接：<a href="https://jacobfilipp.com/DrDobbs/articles/CUJ/2001/0105/meyers/meyers.htm">https://jacobfilipp.com/DrDobbs/articles/CUJ/2001/0105/meyers/meyers.htm</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Andrew Lucas. <a href="https://www.embedded.com/reduce-c-language-coding-errors-with-x-macros-part-1/">&ldquo;Reduce C-language coding errors with X macros&rdquo;</a>, Embedded.com, 2013.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>劈开 Ctrl+Space</title><link>https://victorhge.github.io/zh/posts/ctrl_space/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0800</pubDate><guid>https://victorhge.github.io/zh/posts/ctrl_space/</guid><description>&lt;p&gt;Emacs 拥有大量默认快捷键绑定，不可避免地会与系统快捷键发生冲突，对于 Emacs 新手来说，如何解决这些冲突是一项重大挑战。&lt;/p&gt;
&lt;p&gt;其中最常见的一个是命令 &lt;code&gt;set-mark-command&lt;/code&gt; ，默认键绑定是&lt;code&gt;C-SPC(Ctrl+Space)&lt;/code&gt; &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;。在 Windows 系统中， &lt;code&gt;Ctrl+Space&lt;/code&gt; 默认用于切换中英文输入法，这个冲突会导致在 Windows 下的 Emacs 无法使用 &lt;code&gt;Ctrl+Space&lt;/code&gt; 进行标记设置。&lt;/p&gt;</description><content:encoded><![CDATA[<p>Emacs 拥有大量默认快捷键绑定，不可避免地会与系统快捷键发生冲突，对于 Emacs 新手来说，如何解决这些冲突是一项重大挑战。</p>
<p>其中最常见的一个是命令 <code>set-mark-command</code> ，默认键绑定是<code>C-SPC(Ctrl+Space)</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。在 Windows 系统中， <code>Ctrl+Space</code> 默认用于切换中英文输入法，这个冲突会导致在 Windows 下的 Emacs 无法使用 <code>Ctrl+Space</code> 进行标记设置。</p>
<p>要解决这个问题，可以选择使用 <code>set-mark-command</code> 的备用快捷键 <code>C-@</code> ，或者禁用
Windows 的 <code>Ctrl+Space</code> 功能。此外，由于空格键左右手操作同样方便，还有“第三选择: 把 <code>Ctrl+Space</code> 一分为二，一侧的 <code>Ctrl+Space</code> 留给 Windows，另一侧释放出来供
Emacs 或其他应用程序使用。这正是我采用的方案，因为这样我就可以保留用两个大拇指操作的习惯。</p>
<p>在最新的 Windows 11 下可以这么做：</p>
<ol>
<li>
<p>找到 <code>Settings &gt; Time &amp; Language &gt; Language &amp; region &gt; Options &gt; Microsoft Pinyin &gt; Keys</code> , 取消勾选 <code>Ctrl + Space</code> ，保留勾选 <code>Shift</code> 。<img loading="lazy" src="/ox-hugo/disable_Ctrl+Space.png"></p>
</li>
<li>
<p>打开 PowerToys (没有的话先安装）， 在 Keyboard Manager 功能中，添加一个快捷键重映射：将 <code>Ctrl(Left) + Space</code> 映射为 <code>Shift</code> 或者 <code>Win(Left) + Space</code> 。<img loading="lazy" src="/ox-hugo/remap_shortcuts.png"></p>
</li>
</ol>
<p>此前的 Windows 版本存在一个 UI 无法更改相关设置的缺陷<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>，可以通过修改注册表解决：</p>
<ol>
<li>找到 <code>HKEY_CURRENT_USER\Control Panel\Input Method\Hot Keys\00000010</code> 条目，
将其中的 <code>Key Modifiers</code> 修改为 <code>02 80 00 00</code> , 或者 <code>02 40 00 00</code> <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>。</li>
<li>如果希望将更改应用于所有新用户，还需要在 <code>HKEY_USERS\.DEFAULT\Control Panel\Input Method\Hot Keys\00000010</code> 中进行同样的修改。</li>
</ol>
<p>修改完成后，重启电脑即可生效。</p>
<p>似乎只有 <code>Ctrl+Space</code> 才能“劈开”， 其他的键天然的只对一只手来说顺手，比如<code>Ctrl+a</code> 就已经隐含着 <code>Right Ctrl+a</code> 。</p>
<h2 id="脚注">脚注</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Emacs 有独特的键位的缩写习惯，用 <code>C-SPC</code> 代表 <code>Ctrl+Space</code> 。本文暂且保留常用习惯。&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://superuser.com/questions/327479/ctrl-space-always-toggles-chinese-ime-windows-7">https://superuser.com/questions/327479/ctrl-space-always-toggles-chinese-ime-windows-7</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>02 指Ctrl, 80 指左侧，40指右侧<a href="https://learn.microsoft.com/en-us/windows/win32/tsf/tf-mod--constants">https://learn.microsoft.com/en-us/windows/win32/tsf/tf-mod--constants</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>交换 Ctrl 和 Alt 键</title><link>https://victorhge.github.io/zh/posts/swap_ctrl_alt/</link><pubDate>Wed, 25 Feb 2026 20:59:00 +0800</pubDate><guid>https://victorhge.github.io/zh/posts/swap_ctrl_alt/</guid><description>&lt;figure&gt;
&lt;img loading="lazy" src="https://victorhge.github.io/ox-hugo/lisp-machine-keyboard-2.jpg"/&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;这篇修改过&lt;strong&gt;翻译腔&lt;/strong&gt;的问题。原文放在后面，留作纪念。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="修订版"&gt;修订版&lt;/h2&gt;
&lt;p&gt;这是我所有计算机上的必备配置，全是因为 Emacs。&lt;/p&gt;
&lt;p&gt;Emacs 最早是 TECO 编辑器的宏程序(&lt;a href="https://blog.djmnet.org/2008/08/05/origin-of-emacs/"&gt;Editing MACroS&lt;/a&gt;)。当年开发者用的键盘，Ctrl 键在空格键两侧&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;，大拇指就能按，所以 Emacs 最常用的命令都是用 Ctrl 修饰。不知为何，后来的个人电脑标准键盘把 Ctrl 移到了最边上，在这些键盘上使用 Emacs 就显得不太顺手。&lt;/p&gt;</description><content:encoded><![CDATA[<figure>
    <img loading="lazy" src="/ox-hugo/lisp-machine-keyboard-2.jpg"/> 
</figure>

<blockquote>
<p>这篇修改过<strong>翻译腔</strong>的问题。原文放在后面，留作纪念。</p>
</blockquote>
<h2 id="修订版">修订版</h2>
<p>这是我所有计算机上的必备配置，全是因为 Emacs。</p>
<p>Emacs 最早是 TECO 编辑器的宏程序(<a href="https://blog.djmnet.org/2008/08/05/origin-of-emacs/">Editing MACroS</a>)。当年开发者用的键盘，Ctrl 键在空格键两侧<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>，大拇指就能按，所以 Emacs 最常用的命令都是用 Ctrl 修饰。不知为何，后来的个人电脑标准键盘把 Ctrl 移到了最边上，在这些键盘上使用 Emacs 就显得不太顺手。</p>
<p>我的解决办法是把 Ctrl 和 Alt 互换，让 Ctrl 回到大拇指能按到的位置。这样就可以用大拇指来操作 Emacs 最常用的命令，既轻松又高效。</p>
<p>具体方法：</p>
<ul>
<li>Windows 用<a href="https://github.com/randyrants/sharpkeys"> SharpKeys </a> 改键位；</li>
<li>Linux (GNOME 桌面)用 GNOME Tweak Tool直接设置<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</li>
</ul>
<p>这是一个“一旦用上就再也回不去”的设置。它还有一个“隐藏功能”——让试图操作我键盘的人抓狂。</p>
<h2 id="原版">原版</h2>
<p>这是我所有所用计算机上的必备配置，全是因为 Emacs。</p>
<p>Emacs 最初是为 TECO 编辑器开发的宏程序(<a href="https://blog.djmnet.org/2008/08/05/origin-of-emacs/">Editing MACroS</a>)。当时的开发者所用键盘布局将 Ctrl 键放在空格键两侧<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>，便于大拇指操作。不知什么原因，微型机普及后的标准键盘却将 Ctrl 键移至最外侧，只能靠小拇指操作。这种设计容易导致小拇指疲劳。</p>
<p>因此，我选择交换 Ctrl 和 Alt 键，让 Ctrl 键重回大拇指位置，既缓解小拇指负担，又显著提升效率。</p>
<p>我的具体方法是：</p>
<ul>
<li>Windows：通过<a href="https://github.com/randyrants/sharpkeys"> SharpKeys </a>实现键位重映射。</li>
<li>Linux (GNOME 桌面)：GNOME Tweak Tool可轻松完成设置<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</li>
</ul>
<p>这是一个“一旦用上就回不去”的配置。它还有一个“隐藏功能”——让其他尝试操作我键盘的人抓狂。</p>
<h2 id="脚注">脚注：</h2>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Knight Keyboard <a href="http://xahlee.info/kbd/knight_keyboard.html">http://xahlee.info/kbd/knight_keyboard.html</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>GNOME Treak Tool <a href="https://askubuntu.com/questions/885045/how-to-swap-ctrl-and-alt-keys-in-ubuntu-16-04/885047">https://askubuntu.com/questions/885045/how-to-swap-ctrl-and-alt-keys-in-ubuntu-16-04/885047</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>