<?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>Programming on 海市如幻，雕虫求真</title><link>https://victorhge.github.io/zh/tags/programming/</link><description>Recent content in Programming 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/tags/programming/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></channel></rss>