<?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>design on Kohsruhe</title>
    <link>https://www.kohsruhe.com/zh/tag/design/</link>
    <description>Recent content in design on Kohsruhe</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh</language>
    <managingEditor>kohsruhe@outlook.com (Leehyon HNG)</managingEditor>
    <webMaster>kohsruhe@outlook.com (Leehyon HNG)</webMaster>
    <lastBuildDate>Mon, 25 May 2026 14:08:27 +0800</lastBuildDate>
    
    <atom:link href="https://www.kohsruhe.com/zh/tag/design/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>嵌入式系统的可预测性</title>
      <link>https://www.kohsruhe.com/zh/2026/05/from-working-code-to-predictable-systems/</link>
      <pubDate>Mon, 25 May 2026 14:08:27 +0800</pubDate>
      <author>kohsruhe@outlook.com (Leehyon HNG)</author>
      <guid>https://www.kohsruhe.com/zh/2026/05/from-working-code-to-predictable-systems/</guid>
      <description>&lt;p&gt;最近在修 QAC 报的一些错误，也因此重新认识了很多规则。比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;POD&lt;/li&gt;
&lt;li&gt;&lt;code&gt;constexpr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;禁止全局构造&lt;/li&gt;
&lt;li&gt;控制 &lt;code&gt;static&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Init 顺序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;开始的时候，我不以为然，只是当成要做的任务一样，枯燥乏味，但总是要做的。唯一的成就感是看着自己负责模块的 issues 数字越来越少，当然里面有很大一部分是通过 &lt;code&gt;// PRQA S&lt;/code&gt; 给屏蔽掉了。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>最近在修 QAC 报的一些错误，也因此重新认识了很多规则。比如：</p>
<ul>
<li>POD</li>
<li><code>constexpr</code></li>
<li>禁止全局构造</li>
<li>控制 <code>static</code></li>
<li>Init 顺序</li>
</ul>
<p>开始的时候，我不以为然，只是当成要做的任务一样，枯燥乏味，但总是要做的。唯一的成就感是看着自己负责模块的 issues 数字越来越少，当然里面有很大一部分是通过 <code>// PRQA S</code> 给屏蔽掉了。</p>
<p>后来，不能说是“良心发现”，大概率是不想浪费了时间却还是一知半解，这也归因于我对代码还是有点洁癖的，便开始思考规则背后的缘由，整理如下。</p>
<h2 id="能跑的代码为什么仍然是错误的">能跑的代码，为什么仍然是“错误”的</h2>
<p>在一般的软件开发中，能跑就行，我们平时也看到过很多梗图。确实如此，比如 PC 上，大不了杀了重启。但在嵌入式，能跑可能还不太够。就比如哪天你开着导航还唱着歌，车机突然黑屏重启了，这忍忍也能接受，但如果涉及到驾驶安全，我们需要系统不仅“能运行”，而且要非常“可靠”。</p>
<p>所以，嵌入式系统更关心的是另一件事：</p>
<blockquote>
<p>系统行为是否可预测。</p>
</blockquote>
<h2 id="为什么可预测性是嵌入式汽车电子的核心">为什么“可预测性”是嵌入式（汽车电子）的核心</h2>
<p>嵌入式系统不是运行在资源无限、调度宽松的环境里。它通常面对的是：</p>
<ul>
<li>固定周期任务</li>
<li>有限 RAM / Flash</li>
<li>中断打断</li>
<li>严格的启动顺序</li>
<li>明确的实时性要求</li>
</ul>
<p>在这样的系统里，代码不只是“实现功能”，它还定义了系统在时间、顺序和状态上的行为。如果这些行为不可预测，系统就很难被验证，也很难被信任。</p>
<h2 id="这些规则在解决什么本质问题">这些规则在解决什么“本质问题”</h2>
<p>表面上看，QAC 在限制很多东西：</p>
<ul>
<li>不要随便用全局变量</li>
<li>不要全局构造复杂对象</li>
<li>尽量使用 <code>constexpr</code></li>
<li>控制 <code>static</code></li>
<li>明确初始化顺序</li>
<li>避免隐式依赖</li>
</ul>
<p>但本质上，它们是在减少三类不确定性。</p>
<h3 id="1-时间的不确定性">1. 时间的不确定性</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></div><p>这段代码看起来很普通，但在嵌入式里，问题是：</p>
<ul>
<li>是否会触发动态分配？</li>
<li>分配需要多久？</li>
<li>是否可能失败？</li>
<li>是否会造成堆碎片？</li>
<li>最坏执行时间是多少？</li>
</ul>
<p>如果这些问题无法回答，那它就不适合出现在实时路径里，因为没法估算可执行时间。</p>
<h3 id="2-顺序的不确定性">2. 顺序的不确定性</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// A.cpp
</span></span></span><span class="line"><span class="cl"><span class="n">Foo</span> <span class="n">foo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// B.cpp
</span></span></span><span class="line"><span class="cl"><span class="n">Bar</span> <span class="nf">bar</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</span></span></code></pre></div><p>这类代码最大的问题不是语法，而是初始化顺序不一定由你控制。所以规范会要求：</p>
<ul>
<li>禁止复杂全局构造</li>
<li>全局对象只允许 POD / <code>constexpr</code></li>
<li>复杂初始化放到显式 <code>Init()</code> 阶段</li>
<li>谨慎使用静态局部变量</li>
<li>避免跨文件初始化依赖</li>
</ul>
<p>本质上，是把“隐式顺序”变成“显式顺序”。</p>
<h3 id="3-状态的不确定性">3. 状态的不确定性</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="n">state</span><span class="p">;</span>
</span></span></code></pre></div><p>单就这行代码没啥问题，但如果它被多个任务、中断或模块间接访问，问题就变复杂了。全局变量和静态变量的问题，不只是“作用域大”。更大的问题是：</p>
<blockquote>
<p>谁能改它？什么时候改？是否并发访问？是否有生命周期约束？</p>
</blockquote>
<p>所以好的设计会尽量让状态：</p>
<ul>
<li>有明确 owner</li>
<li>有明确生命周期</li>
<li>有明确访问边界</li>
<li>有明确同步策略</li>
</ul>
<h2 id="尽量让系统在运行前就已经确定">尽量让系统在运行前就已经确定</h2>
<p>所以，不难理解：</p>
<ul>
<li><code>constexpr</code> 的价值，是把计算提前到编译期</li>
<li>POD 的价值，是避免运行期构造逻辑</li>
<li>禁止全局复杂对象的价值，是避免启动阶段出现不可控行为</li>
<li>控制 <code>static</code> 的价值，是避免隐藏状态和隐式初始化</li>
<li>显式初始化顺序的价值，是让依赖关系写在代码里，而不是交给编译器、链接器或运行时</li>
</ul>
<h2 id="思考模型">思考模型</h2>
<p>以后写嵌入式 C++ 时，我觉得可以反复问自己四个问题：</p>
<ul>
<li>它什么时候初始化？编译期？启动？第一次调用？</li>
<li>它依赖什么？</li>
<li>它耗时是否固定？</li>
<li>它的状态谁能改？</li>
</ul>
<p>如果以上问题都能全部回答，那你的设计就是嵌入式友好的。</p>
<p>嵌入式 C++ 的所有“限制”，本质不是限制功能，而是把 C++ 从“自由系统”变成“可预测”，进而“可证明”。</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>