<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>C++ on Yu's Space</title><link>https://code-agree.github.io/tags/c++/</link><description>Recent content in C++ on Yu's Space</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 24 Aug 2026 19:30:00 +0800</lastBuildDate><atom:link href="https://code-agree.github.io/tags/c++/index.xml" rel="self" type="application/rss+xml"/><item><title>SIMD Parser 原理精读:把逐字节状态机编译成位运算数据流</title><link>https://code-agree.github.io/blog/2026-08-24-simd_parser/</link><pubDate>Mon, 24 Aug 2026 19:30:00 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-08-24-simd_parser/</guid><description>上一篇《SIMD 深入解析:从硬件原理到加密货币 HFT 中的应用》(下称&amp;quot;硬件篇&amp;quot;)讲了 SIMD 的硬件机理与加密 HFT 链路全景,其中 §10.3 说&amp;quot;simdjson 比逐字符解析快约 10 倍&amp;quot;,但没有回答为什么能快——毕竟解析看起来是最&amp;quot;串行&amp;quot;的活:每个字节的含义取决于它前面的所有字节。这一篇专门拆这个问题:SIMD parser 如何把一个逐字节状态机改写成位运算数据流。其中最漂亮的一击,是用一条乘法指令跑完 64 步状态转移。
1. 先理解敌人:parser 是 CPU 最不擅长的负载 #一个标量 JSON parser 的本质是逐字节状态机:
for (each byte c) { switch (state) { case IN_STRING: if (c == &amp;#39;&amp;#34;&amp;#39;) state = OUT; else if (c == &amp;#39;\\&amp;#39;) state = ESC; break; case OUT: if (c == &amp;#39;{&amp;#39;) push(OBJ); else if (c == &amp;#39;&amp;#34;&amp;#39;) state = IN_STRING; break; ... } } 这段代码同时踩中现代 CPU 的两个死穴:</description></item><item><title>SIMD 深入解析:从硬件原理到加密货币 HFT 中的应用</title><link>https://code-agree.github.io/blog/2026-08-24-simd/</link><pubDate>Mon, 24 Aug 2026 17:10:28 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-08-24-simd/</guid><description>前半部分把 SIMD 的硬件机理从零讲透:寄存器、执行单元、数据布局、内存边界、自动向量化、频率代价;后半部分逐段拆解加密货币 HFT 的 tick-to-trade 链路,回答&amp;quot;SIMD 到底在哪里赚钱&amp;quot;。
0. 你天天都在用 SIMD #先建立一个认知:哪怕你从没写过一行向量代码,你的程序也早就在享受 SIMD 了——memcpy/memcmp/strlen 的 glibc 实现内部全是向量指令;absl::flat_hash_map 查找快的秘密是一条 SIMD 指令同时比对 16 个候选槽位;simdjson 解析 JSON 快十倍,名字就写在脸上;你写个普通的数组循环开 -O3,编译器会自动把它改写成 SIMD 版本。
但&amp;quot;被动享受&amp;quot;和&amp;quot;主动驾驭&amp;quot;之间差一层原理。这篇文章先把原理讲清,再回到加密货币 HFT 的具体链路上看它值多少钱。
1. 本质:一条指令,多份数据 #假设要把两个数组对应位置相加:
float a[8] = {1, 2, 3, 4, 5, 6, 7, 8}; float b[8] = {10, 20, 30, 40, 50, 60, 70, 80}; float c[8]; for (int i = 0; i &amp;lt; 8; i++) c[i] = a[i] + b[i]; 普通写法下 CPU 做 8 轮,每轮加 1 个数。SIMD(Single Instruction, Multiple Data)的做法:把 a 的 8 个数一口气装进一个&amp;quot;加宽版寄存器&amp;quot;,b 的 8 个数装进另一个,用一条加法指令让 8 对数同时相加:</description></item><item><title>MengRao/SPSC_Queue 源码精读：把跨核延迟做到 50ns 的四种 SPSC 队列设计</title><link>https://code-agree.github.io/blog/2026-08-07-spsc_queue_mengrao/</link><pubDate>Fri, 07 Aug 2026 09:00:00 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-08-07-spsc_queue_mengrao/</guid><description>MengRao/SPSC_Queue 是国内低延迟圈知名开发者饶萌（tcpshm、fmtlog 的作者）开源的单生产者单消费者无锁队列，README 宣称 10–200B 消息的跨核通信延迟在 50–100ns。整个仓库只有 4 个头文件、每个不到 150 行，却把 SPSC 队列设计空间里几乎所有关键取舍都覆盖了：定长 vs 变长消息、IPC crash-safe vs 极致延迟。本文逐个拆解这四个实现，重点回答一个问题：同样是无锁环形队列，凭什么它能比教科书写法快一倍？拆完源码后，文章会再退一步补齐全景：支撑这一切的寻址/缓存一致性/内存序三层地基，以及走出 SPSC 之后 MPSC/MPMC 的核心机制与选型。
一、仓库总览：一个 2×2 设计矩阵 #四个头文件不是四个孤立实现，而是两个维度的组合：
定长消息（模板参数 T） 变长消息（带 MsgHeader） 原子发布，crash-safe，可用于共享内存 IPC SPSCQueue.h SPSCVarQueue.h（用于作者的 tcpshm 框架） 极致延迟优化，仅限线程间 SPSCQueueOPT.h SPSCVarQueueOPT.h 两行的本质区别只有一条：消费者靠什么发现新消息。
基础版：消费者轮询生产者发布的共享索引 write_idx——发现消息要读 两条缓存行（索引一条、数据一条）； OPT 版：把&amp;quot;有没有消息&amp;quot;这个标志内嵌进数据所在的缓存行——发现消息只要读 一条缓存行。 跨核通信的延迟基本上就是缓存行在两个核之间传输的次数 × 单次传输耗时（几十 ns）。OPT 版把次数从 2 降到 1，延迟近乎减半——这是整个仓库最核心的亮点。代价是发布操作从&amp;quot;单条原子 store&amp;quot;变成多步写入，进程中途崩溃会把队列留在不一致状态，所以 OPT 版不能用于共享内存 IPC。
在深入源码之前，先交代两件事：这类队列在设计空间里的位置（它是&amp;quot;队列&amp;quot;，不是&amp;quot;广播&amp;quot;），以及四个实现共享的三条&amp;quot;设计基因&amp;quot;。
延伸阅读：并发原语的硬件成本（MESI、原子指令、六种内存序）可以先看这篇打底：并发原语深度剖析：从 Mutex 到 Atomic 到 Lock-Free 数据结构
二、队列还是广播：一个先于所有代码的设计决策 #深入源码前值得先停一步。SPSC_Queue 的一切设计都建立在一个前提上：读者的消费进度（read_idx）放在共享结构里，写者看得见。这个决策先于所有实现细节，直接划定了这类队列的能力边界——它的对立面是把读进度私有化的&amp;quot;广播&amp;quot;模型：</description></item><item><title>共享内存 IPC 深度实测：从 iceoryx 到自研 SPSC，HFT 场景下的终极选型</title><link>https://code-agree.github.io/blog/2026-04-17-iceoryx_ipc_benchmark/</link><pubDate>Fri, 17 Apr 2026 12:00:00 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-04-17-iceoryx_ipc_benchmark/</guid><description>本文从实测出发，完整覆盖三个层次的共享内存 IPC 方案：iceoryx（工业级零拷贝框架）、Aeron（全栈消息系统）、自研 SPSC Ring Buffer（极致低延迟）。通过同平台 benchmark 对比、源码级热路径分析、跨进程 atomic 原理拆解，回答一个核心问题：HFT 的进程间通信到底该怎么选？
一、测试环境 # 项目 详情 机器 MacBook Pro (Mac16,8) CPU Apple M4 Pro, 14 核 (10 Performance @ 4.51GHz + 4 Efficiency @ 2.74GHz) 内存 24 GB 统一内存 OS macOS 26.3.1 (Darwin 25.3.0, arm64) 内核 xnu-12377.91.3 RELEASE_ARM64_T6041 编译器 Apple Clang 15.0.0 (clang-1500.3.9.4) C++ 标准 C++17 构建类型 Release (-O3 -DNDEBUG) Sanitizers 全部关闭 (ASAN/TSAN OFF) iceoryx 版本 v2.95.8 (commit 15dc8ed05) 二、iceoryx 性能实测 #2.</description></item><item><title>HFT 热路径错误处理：异常机制、optional 与最佳实践</title><link>https://code-agree.github.io/blog/2026-03-04-try_catch/</link><pubDate>Wed, 04 Mar 2026 10:24:45 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-03-04-try_catch/</guid><description>本文从 C++ 异常机制与 std::optional&amp;lt;T&amp;gt; 的实现原理出发，说明二者在延迟与可预测性上的差异，并给出在高频交易（HFT）热路径上的错误处理最佳实践。
1. 为什么热路径要单独考虑错误处理 #在 HFT 系统中，报价、下单、风控等逻辑往往在热路径上执行：每笔行情或每次 tick 都会触发，执行频率高、对延迟和抖动极其敏感。错误处理方式会直接影响：
延迟：异常抛出时的栈展开、析构会带来不可预测的延迟尖刺； 可预测性：热路径上应尽量无分支或分支可预测，避免因“可能失败”的 API 引入额外分支或控制流； 局部失败隔离：单个标的或档位失败不应拖垮整次处理，但实现方式不能以牺牲延迟为代价。 因此，热路径上的错误处理需要显式设计，而不是依赖“通用”的异常或包装类型。
2. C++ 异常机制（try-catch）原理 #2.1 基本流程 #当程序执行到 throw expr 时：
构造异常对象：用 expr 构造一个异常对象（可能拷贝或移动）； 栈展开（stack unwinding）：从当前 throw 点开始，沿调用栈向上逐层退出，每退出一层就析构该层中的局部对象（按构造的逆序）； 查找 catch：在调用栈上寻找与异常类型匹配的 catch 子句； 匹配成功：进入 catch 块执行，然后从 catch 之后继续执行（或返回到调用方）； 匹配失败：若一直到 main 仍未匹配，则调用 std::terminate()，进程终止（可能产生 coredump）。 因此：
有 catch：异常被接住 → 栈展开 + 执行 catch → 程序继续运行，但本帧已付出栈展开和析构的代价； 无 catch：未捕获异常 → std::terminate() → 进程退出，不再存在“本帧延迟”的问题。 热路径上若使用 try-catch，我们讨论的是“有 catch、程序继续”的情形：不抛时几乎不付出额外成本，一旦抛就会在本帧产生一次高延迟。
2.2 Zero-cost 异常模型：成本被挪去了哪里 #主流编译器（GCC、Clang、64 位 MSVC）采用表驱动的零成本异常：正常路径不插入任何登记指令（老的 SJLJ 模型每进一次 try 都要 setjmp 登记，人人路路付费）；异常处理所需的信息被编译成静态表（.</description></item><item><title>并发原语与内存序深度剖析：从 Mutex 到 Atomic 到 Lock-Free 数据结构</title><link>https://code-agree.github.io/blog/2026-03-03-mutext/</link><pubDate>Tue, 03 Mar 2026 23:39:27 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-03-03-mutext/</guid><description>本文系统梳理 Linux/x86_64 环境下各种并发同步机制的实现原理、硬件行为和性能开销，并完整讲解 C++ 内存模型与六种内存序的语义、典型误用与调试方法，为 HFT 场景下的并发设计提供决策依据。本文是本站内存序主题的权威出处，其他文章涉及内存序时均链接至此。
一、硬件基础：理解开销的根源 #在讨论任何同步原语之前，必须先理解 CPU 缓存一致性协议，因为所有同步开销的本质都是 cache line 在核间的传输。
1.1 MESI 协议 #现代多核 CPU 通过 MESI 协议（Modified, Exclusive, Shared, Invalid）维护缓存一致性：
Core 0 Core 1 ┌──────────┐ ┌──────────┐ │ L1 Cache │ │ L1 Cache │ │ Line X: │ │ Line X: │ │ Modified │ │ Invalid │ └────┬─────┘ └────┬─────┘ │ │ └──────┬──────────────┘ │ ┌──────┴──────┐ │ L3 Cache │ (或 Directory) │ / Ring Bus│ └─────────────┘ 四种状态：</description></item><item><title>段错误调试分析：从系统日志到代码修复</title><link>https://code-agree.github.io/blog/2026-01-19-core_ana/</link><pubDate>Mon, 19 Jan 2026 22:53:59 +0800</pubDate><guid>https://code-agree.github.io/blog/2026-01-19-core_ana/</guid><description>摘要 #本文详细记录了一次段错误（SEGV）的完整调试过程。通过系统日志分析、地址解析、汇编代码分析和代码审查，成功定位并修复了 PerformanceMonitor 中的数组越界问题。本文展示了如何在没有完整 coredump 文件的情况下，仅凭系统日志和调试工具进行问题定位。
一、问题现象 #1.1 崩溃信息 #从 coredumpctl 获取的崩溃信息：
PID: 178581 (panda_strategy-) Signal: 11 (SEGV) Timestamp: Mon 2026-01-19 21:25:12 CST Executable: /home/jason/panda/panda-strategy/Strategy/out/build/wsl-profile/panda_strategy-1.0.0 Message: Coredump entry has no core attached 关键问题：coredump 文件未保存，无法使用 GDB 直接分析。
1.2 崩溃时间点分析 #从日志文件 main.log 中可以看到：
[21:25:12.816398][178634][info][strategy] Funding rates updated, total entries: 93, updated entries: 5 [21:25:12.816474][178634][info][strategy] Updated tradability parameters: 274 margin entries [21:25:12.816493][178634][info][strategy] Reference updated for 3 symbols [21:25:12.816502][178634][info][strategy] Fee rate table updated, total entries: 36, updated entries: 36 [21:25:12.</description></item><item><title>HFT 热路径中的分支：从 CPU 流水线到尾延迟治理的完整工程指南</title><link>https://code-agree.github.io/blog/2025-12-26-if_pre/</link><pubDate>Fri, 26 Dec 2025 12:32:42 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-12-26-if_pre/</guid><description>高频交易系统追求微秒甚至纳秒级的稳定性。&amp;ldquo;热路径少写 if&amp;ldquo;是常见的优化建议，但这个规则需要精确理解：if 的性能代价不来自语法层面，而来自 CPU 微架构层面的流水线行为。本文从 CPU 执行机理出发，构建完整的工程决策框架。
1. 前置知识：CPU 流水线执行模型 #1.1 流水线的本质：并行与预测 #现代 x86 CPU 采用超标量乱序执行架构，将指令执行分解为多个阶段并行推进：
前端（Front-end） 后端（Back-end） ┌─────────────────┐ ┌──────────────────┐ │ Fetch (I-cache) │──→ │ Schedule/Dispatch│ │ Decode (µop) │──→ │ Execute (ALU/LSU)│ │ Branch Predict │──→ │ Memory Access │ │ Rename (RAT) │──→ │ Retire (ROB) │ └─────────────────┘ └──────────────────┘ 关键机制：
深度流水线（典型 14-20 级）需要提前知道下一步执行什么 乱序执行（OoO）可以在 Reorder Buffer (ROB) 内并行处理 ~224 条 µops（Skylake） 推测执行（Speculative Execution）在分支结果未知时继续执行 1.2 为什么需要分支预测 #控制流指令（if/switch/call）会改变下一条指令地址。如果等待条件计算完成，流水线将停顿。现代 CPU 使用：</description></item><item><title>C++ Map 容器性能差异的底层实现分析：std::map vs std::unordered_map vs absl::flat_hash_map vs absl::node_hash_map</title><link>https://code-agree.github.io/blog/2025-12-26-various_map/</link><pubDate>Fri, 26 Dec 2025 01:32:39 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-12-26-various_map/</guid><description>C++ Map 容器性能差异的底层实现分析：std::map vs std::unordered_map vs absl::flat_hash_map vs absl::node_hash_map #摘要 #本文基于实际的性能基准测试结果，从底层数据结构和内存布局的角度深入分析 std::map、std::unordered_map、absl::flat_hash_map 和 absl::node_hash_map 四种容器在不同操作场景下的性能差异。测试结果表明，在查找密集型场景中，absl::flat_hash_map 相比传统 std::map 有近 3 倍的性能提升。
一、测试结果概览 #基于 50 个元素、100 万次查找操作的基准测试结果：
容器类型 查找耗时 相对性能 插入耗时 混合操作耗时 std::map 44.878 ms 1.00x (基准) 2.323 ms 29.211 ms std::unordered_map 25.400 ms 1.77x 1.706 ms 18.323 ms absl::node_hash_map 16.179 ms 2.77x 1.816 ms 13.714 ms absl::flat_hash_map 15.329 ms 2.93x 1.867 ms 13.138 ms 二、数据结构底层实现分析 #2.1 std::map：红黑树实现 #2.1.1 内存布局 #std::map 基于**红黑树（Red-Black Tree）**实现，是一种自平衡二叉搜索树。每个节点包含：</description></item><item><title>std::map vs absl::flat_hash_map 性能比较分析</title><link>https://code-agree.github.io/blog/2025-12-24-flat_hash_map/</link><pubDate>Wed, 24 Dec 2025 17:13:38 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-12-24-flat_hash_map/</guid><description>一、底层实现原理 #1.1 std::map 实现原理 #std::map 是基于**红黑树（Red-Black Tree）**实现的有序关联容器。
红黑树特性 # 自平衡二叉搜索树：保证最坏情况下 O(log n) 的查找、插入、删除时间复杂度 有序性：元素按照键值自动排序（基于 operator&amp;lt; 或自定义比较器） 内存布局：节点式存储，每个节点包含： 键值对（key-value pair） 左子节点指针 右子节点指针 父节点指针 颜色标记（红/黑） 查找过程 #查找键 K： 1. 从根节点开始 2. 比较 K 与当前节点键值 3. 如果 K &amp;lt; 当前键，进入左子树 4. 如果 K &amp;gt; 当前键，进入右子树 5. 如果 K == 当前键，返回节点 6. 重复步骤 2-5，直到找到或到达叶子节点 时间复杂度：O(log n)，其中 n 为树中节点数
平均比较次数：log₂(n) 对于 20 个元素：log₂(20) ≈ 4.3 次比较 对于 100 个元素：log₂(100) ≈ 6.6 次比较 插入过程 #插入键值对 (K, V)： 1.</description></item><item><title>SPSC DTO 构造语义优化复盘</title><link>https://code-agree.github.io/blog/2025-11-08-default/</link><pubDate>Sat, 08 Nov 2025 00:47:42 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-11-08-default/</guid><description>背景 #最近一次 panda-datatype 提交中，LockFreeRingBuffer 的读写索引放宽为 memory_order_relaxed，同时将行情/订单相关 DTO（Depth5、OrderUpdate、TradeUpdate 等）的默认构造方式改为“成员内联默认值 + = default”。这看似语法微调，实则是为单生产者单消费者（SPSC）数据通道消除隐藏成本。
旧实现的隐患 #旧版在 .cpp 里手写构造函数逐字段清零，逻辑直观，却带来三个问题：
失去平凡属性：自定义构造/析构一旦出现，类型就不再是 trivially constructible/copyable，无法放心地按位拷贝。无锁结构如 LockFreeRingBuffer&amp;lt;std::array&amp;lt;T&amp;gt;&amp;gt; 一旦直接覆盖槽位，轻则报错、重则 UB。 破坏无锁假设：SPSC 设计假定“写入即内存搬运”。如果数组槽位里的 T 有非平凡构造，buffer[index] = item; 就不得不调用它，整条“无锁 + 放宽内存序”路径失效。 维护负担重：新增字段必须同步修改 .cpp 构造，否则默认值遗漏；默认值散落在实现文件，代码审查也不直观。 新方案：成员默认值 + 默认化构造 #提交中 OrderUpdate 的具体修改如下（TradeUpdate 类似）：
struct OrderUpdate { Exchange m_exchange = Exchange::UNKNOWN; uint32_t m_accountId = 0; std::array&amp;lt;char, 32&amp;gt; m_clientOrderId = {}; OrderStatus m_status = OrderStatus::UNKNOWN; int32_t m_errorStatusCode = 0; std::array&amp;lt;char, 64&amp;gt; m_errorReason = {}; OrderUpdate() = default; }; Depth5 也简化为：</description></item><item><title>C++数组类型的底层原理分析</title><link>https://code-agree.github.io/blog/2025-07-17-c++origin_array/</link><pubDate>Thu, 17 Jul 2025 03:21:09 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-07-17-c++origin_array/</guid><description>std::array 与 int a[10] 的区别与优点 #从类型系统角度 #std::array 是一个类模板，而 int a[10] 是一个内建数组类型。这个根本区别导致了它们在C++类型系统中的行为差异。
类型退化 #传统数组 int a[10] 在作为函数参数传递时会退化(decay)为指针 int*，导致数组大小信息丢失。这种退化是C语言遗留问题，在C++中仍然存在：
void func(int arr[10]) { // 实际上arr的类型是int*，大小信息已丢失 sizeof(arr); // 返回指针大小，而非数组大小 } 而 std::array 是一个完整的对象类型，传递时保留其完整类型信息：
void func(std::array&amp;lt;int, 10&amp;gt;&amp;amp; arr) { sizeof(arr); // 正确返回整个数组大小 } 作为值类型 #std::array 是真正的&amp;quot;值类型&amp;quot;，可以:
完整复制（不会退化为指针） 用于函数返回值 在STL容器中存储 参与比较操作（支持==, &amp;lt;等运算符） 不可赋值性与返回限制的底层原理 #原生数组存在两个重要限制：
T buffer1[10]; T buffer2[10]; buffer1 = buffer2; // 编译错误 auto get_buffer() -&amp;gt; ??? { return buffer; // 错误：无法直接返回原生数组 } 1. 不能直接赋值的底层原理：</description></item><item><title>C++17核心特性深度解析：constexpr if、std::optional与std::string_view</title><link>https://code-agree.github.io/blog/2025-07-17-c++17_new_feature/</link><pubDate>Thu, 17 Jul 2025 02:07:40 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-07-17-c++17_new_feature/</guid><description>引言 #C++17作为C++标准的重要里程碑，引入了众多革命性的特性，其中constexpr if、std::optional和std::string_view三个特性在性能优化和代码表达力方面具有深远影响。本文将深入解析这三个特性的设计理念、实现机制，以及它们在现代C++开发特别是高性能计算场景中的应用价值。
1. constexpr if：编译时条件分支的革命 # constexper是C++11引入的
1.1 基本概念与语法 #constexpr if是C++17引入的编译时条件语句，允许在模板中根据编译时常量表达式有条件地包含或排除代码分支。
template&amp;lt;typename T&amp;gt; constexpr auto process_data(T data) { if constexpr (std::is_integral_v&amp;lt;T&amp;gt;) { return data * 2; // 只有整数类型才会编译此分支 } else if constexpr (std::is_floating_point_v&amp;lt;T&amp;gt;) { return data * 1.5; // 只有浮点类型才会编译此分支 } else { return data; // 其他类型的默认处理 } } 1.2 与传统SFINAE的对比 #传统SFINAE方式：
// C++11/14 复杂的SFINAE实现 template&amp;lt;typename T&amp;gt; typename std::enable_if_t&amp;lt;std::is_integral_v&amp;lt;T&amp;gt;, T&amp;gt; process_data(T data) { return data * 2; } template&amp;lt;typename T&amp;gt; typename std::enable_if_t&amp;lt;std::is_floating_point_v&amp;lt;T&amp;gt;, T&amp;gt; process_data(T data) { return data * 1.</description></item><item><title>C++ map与unordered_map详解</title><link>https://code-agree.github.io/blog/2025-07-17-map_unordered_map/</link><pubDate>Thu, 17 Jul 2025 01:44:22 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-07-17-map_unordered_map/</guid><description>基本概念 #map（有序映射） # 定义：基于键值对的有序关联容器 头文件：#include &amp;lt;map&amp;gt; 特点：元素按键值自动排序存储 unordered_map（无序映射） # 定义：基于键值对的无序关联容器 头文件：#include &amp;lt;unordered_map&amp;gt; 特点：元素无序存储，通过哈希表实现快速访问 底层实现原理 #map的底层实现：红黑树(BST + 自平衡) #数据结构 #template&amp;lt;typename Key, typename Value&amp;gt; struct MapNode { std::pair&amp;lt;Key, Value&amp;gt; data; // 键值对 MapNode* left; // 左子节点 MapNode* right; // 右子节点 MapNode* parent; // 父节点 bool color; // 红色(true) 或 黑色(false) }; 红黑树特性 # 每个节点要么是红色，要么是黑色 根节点是黑色 所有叶子节点（NIL）是黑色 红色节点的两个子节点都是黑色（不能有连续的红色节点） 从任意节点到其每个叶子的所有简单路径都包含相同数目的黑色节点 平衡机制与时间复杂度分析 #红黑树通过旋转和重新着色维持平衡，这是其时间复杂度为O(log n)的根本原因：
为什么是O(log n)？
树高度控制：红黑树的特性保证了树的高度不会超过2*log₂(n+1) 路径长度限制：最长路径不超过最短路径的2倍 操作路径：查找、插入、删除都沿着从根到叶的路径进行 // 查找操作的时间复杂度分析 Node* find(const Key&amp;amp; key) { Node* current = root; int steps = 0; // 统计步数 while (current !</description></item><item><title>深度解析：Asio在Linux平台的I/O模型本质</title><link>https://code-agree.github.io/blog/2025-07-09-asio/</link><pubDate>Wed, 09 Jul 2025 02:19:26 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-07-09-asio/</guid><description>在现代C++网络编程中，Boost.Asio（或standalone asio）是使用最广泛的异步I/O库之一。然而，关于Asio的I/O模型本质，特别是在Linux平台上的实现机制，存在很多误解。本文将深入剖析Asio在Linux平台的真实面目。
本质定位 #Asio的真实身份 #Asio在Linux平台上的本质：同步非阻塞I/O + I/O多路复用 + 回调机制的高级封装
这意味着：
✅ 不是传统的阻塞I/O：提供了异步编程接口 ❌ 不是真正的异步I/O：底层仍使用同步系统调用 ✅ 是异步编程框架：通过回调机制模拟异步编程体验 核心理解 #// Asio给你的印象（异步风格API） socket.async_read_some(buffer(data), [](error_code ec, size_t bytes) { // 看起来像异步回调 process_data(data, bytes); }); // 但Linux下的实际执行（简化） epoll_wait(epfd, events, 128, -1); // 同步等待事件 ssize_t n = read(fd, buffer, size); // 同步读取 callback(n); // 调用用户回调 关键洞察：Asio提供了异步的编程体验，但不是异步的执行机制。
工作原理 #事件驱动的执行模型 #Asio在Linux上实现了Reactor模式，而不是Proactor模式：
// Reactor模式的典型流程 class AsioReactor { public: void async_read(socket&amp;amp; s, buffer b, handler h) { // 1. 注册读取意图 register_read_intent(s.</description></item><item><title>C++中inline函数为何比普通函数调用更快：深入解析</title><link>https://code-agree.github.io/blog/2025-06-24-inline_function_optimization/</link><pubDate>Tue, 24 Jun 2025 16:16:15 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-24-inline_function_optimization/</guid><description>目录 # 普通函数调用的开销 inline函数的优化 inline函数的局限性与权衡 示例代码与分析 什么时候使用inline函数？ 现代编译器的优化 总结 1. 普通函数调用的开销 #在C++中，普通函数调用涉及以下步骤，这些步骤会引入性能开销：
栈帧的创建与销毁： 调用函数时，程序需要保存当前函数的状态（例如寄存器中的值），为被调用函数分配栈空间，设置栈帧（包括返回地址、参数传递、局部变量等）。 函数返回时，需要恢复调用者的栈帧状态。 这些操作涉及栈指针（SP）和基指针（BP）的调整，以及内存的读写操作。 参数传递与返回值： 参数需要通过栈或寄存器传递到被调用函数，这可能涉及内存拷贝（尤其是对于较大的结构体或对象）。 返回值也需要通过寄存器或内存传递回调用者。 跳转开销： 函数调用需要将程序计数器（PC）跳转到被调用函数的地址（通过call指令），返回时再跳回调用者（通过ret指令）。 对于直接调用（call到已知地址），现代CPU的分支预测器几乎可以做到100%准确，因此很少引发流水线刷新。跳转的主要开销来自call/ret指令本身的执行、栈帧的建立与拆除，以及跳转目标代码可能不在指令缓存（I-Cache）中导致的缓存未命中。对于间接调用（如虚函数调用，通过函数指针跳转），由于目标地址在运行时才确定，分支预测失败的概率较高，此时才更容易导致流水线刷新。 寄存器上下文保存/恢复： 调用函数可能导致寄存器内容的保存与恢复（例如调用者保存的寄存器或被调用者保存的寄存器），增加额外的指令开销。 这些步骤虽然在现代CPU上非常快，但对于频繁调用的函数（例如小型、简单函数），这些开销可能占函数执行时间的显著比例。
2. inline函数的优化 #inline关键字建议编译器将函数的代码直接嵌入到调用处，而不是生成函数调用。这种内联（inlining）优化可以显著减少上述开销，原因如下：
消除函数调用开销： 内联函数的代码直接嵌入到调用处，省去了栈帧的创建与销毁、参数传递、返回值的处理以及跳转指令。 程序无需执行call和ret指令，也避免了可能的流水线刷新。 优化机会增加： 编译器在优化阶段可以看到内联函数的完整代码上下文，可以应用更多的优化技术，例如： 常量折叠：如果内联函数的参数是常量，编译器可以直接计算结果。 死代码消除：如果内联函数中某些分支在调用上下文中永远不会执行，编译器可以剔除这些代码。 循环展开或指令重排：内联后，编译器可以更好地调整指令顺序，优化CPU缓存利用率或减少分支跳转。 减少指令数： 对于小型函数，函数调用的开销可能比函数体本身的执行时间还长。内联后，函数体的代码直接嵌入，减少了额外的指令（如push、pop、call、ret等）。 3. inline函数的局限性与权衡 #虽然inline函数通常更快，但它并非总是最佳选择，以下是一些需要注意的点：
代码膨胀： 内联函数会将函数代码复制到每个调用点，如果函数体较大或调用点很多，可能导致生成的机器代码体积显著增加。这可能导致： 指令缓存（I-Cache）效率下降：代码体积过大可能无法完全放入CPU的指令缓存，增加缓存未命中（cache miss）。 可执行文件变大：增加编译后二进制文件的大小。 编译器的自主决定： inline关键字只是一个建议，现代编译器（如GCC、Clang、MSVC）会根据自己的优化策略决定是否内联。 编译器可能忽略inline关键字（例如函数体过大或过于复杂），也可能自动内联未标记为inline的函数（称为自动内联）。 例如，O2或O3优化级别下，编译器会根据函数的大小、调用频率等因素智能选择是否内联。 递归函数： 递归函数通常无法完全内联，因为内联会导致无限展开。编译器可能只内联部分递归调用（例如尾递归优化）。 调试难度： 内联函数的代码在调试时可能不可见，因为它们被展开后不再作为独立的函数存在，可能影响调试体验。 4. 示例代码与分析 #以下是一个简单的例子，展示普通函数调用与内联函数的差异：
#include &amp;lt;iostream&amp;gt; // 普通函数 int add(int a, int b) { return a + b; } // 内联函数 inline int inline_add(int a, int b) { return a + b; } int main() { int x = 5, y = 10; int result1 = add(x, y); // 普通函数调用 int result2 = inline_add(x, y); // 内联函数调用 std::cout &amp;lt;&amp;lt; result1 &amp;lt;&amp;lt; &amp;#34; &amp;#34; &amp;lt;&amp;lt; result2 &amp;lt;&amp;lt; std::endl; return 0; } 普通函数调用（add）：</description></item><item><title>深入理解 False Sharing：实测原子操作与缓存行对齐对性能的影响</title><link>https://code-agree.github.io/blog/2025-06-23-cache_false_sharing_analysis/</link><pubDate>Mon, 23 Jun 2025 15:39:27 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-23-cache_false_sharing_analysis/</guid><description>目录 #1. 引言 # False Sharing概念介绍 文章研究目标 2. 测试设计概览 # 测试用例矩阵 测试方法说明 3. 样例运行结果 # 普通变量 + False Sharing 普通变量 + 无False Sharing 原子变量 + False Sharing 原子变量 + 无False Sharing 4. 现象分析与原理解释 # False Sharing如何降低性能 alignas(64)避免False Sharing的原理 cache miss升高的原因分析 原子变量性能开销分析 5. 深入理解缓存一致性与原子操作 # MESI缓存一致性协议详解 普通变量与原子变量的对比 原子变量 + False sharing的性能影响 CPU指令层面的差异 缓存一致性协议的影响 微架构层面的详细分析 6. 实战优化建议 # 不同场景的优化策略 7. 总结 # False Sharing的关键要点 核心结论 8. 参考资料与相关阅读 #9. 附录：完整测试代码 # 引言 #在现代多核处理器架构中，缓存系统在性能中扮演着至关重要的角色。然而，当多个线程同时操作位于同一缓存行（Cache Line）内的不同变量时，即使它们并未共享变量本身，也可能导致频繁的缓存一致性协议交互，这就是著名的性能杀手——False Sharing。</description></item><item><title>C++原子操作内存序性能分析：seq_cst vs relaxed</title><link>https://code-agree.github.io/blog/2025-06-21-memory_order_performance_analysis/</link><pubDate>Sat, 21 Jun 2025 00:47:52 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-21-memory_order_performance_analysis/</guid><description>摘要 #本文分析了C++原子操作中不同内存序(memory ordering)对性能的影响，特别是比较了默认的顺序一致性(seq_cst)与宽松(relaxed)内存序在x86-64架构上的性能差异。通过实验测试、性能分析和汇编代码检查，我们发现即使在内存模型较强的x86架构上，不同内存序的选择仍然会产生可测量的性能差异。
1. 实验设计 #1.1 测试程序 #我们设计了两个版本的测试程序，它们在固定时间内执行原子变量的读取和计数操作，唯一区别是原子变量读取时使用的内存序不同：
seq_cst版本 (默认内存序):
void worker_seq_cst() { while (running_) { // 默认使用 seq_cst counter_.fetch_add(1, std::memory_order_relaxed); busy_loop(); } } relaxed版本 (显式指定宽松内存序):
void worker_relaxed_load() { while (running_.load(std::memory_order_relaxed)) { counter_.fetch_add(1, std::memory_order_relaxed); busy_loop(); } } 1.2 编译与执行环境 #测试程序使用以下命令编译：
g++ -std=c++11 -O0 -pthread atomic_test_seq_cst.cpp -o test_gcc_seq_cst g++ -std=c++11 -O0 -pthread atomic_test_relaxed.cpp -o test_gcc_relaxed 每个程序运行5秒钟，记录在此期间完成的操作次数。同时使用perf工具收集性能数据：
perf record -e cpu-clock:pppH ./test_gcc_seq_cst perf record -e cpu-clock:pppH ./test_gcc_relaxed 2. 实验结果 #2.1 执行计数结果 # 版本 操作计数 seq_cst 26,494,108 relaxed 26,660,082 性能差异：</description></item><item><title>LockFreeEventBus：从死锁案例到性能剖析</title><link>https://code-agree.github.io/blog/2025-06-20-lockfree_eventbus_performance_analysis/</link><pubDate>Fri, 20 Jun 2025 14:38:58 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-20-lockfree_eventbus_performance_analysis/</guid><description>概述 #本文是 LockFreeEventBus 的完整记录，分上下两部分：
上篇（第 1–5 节）：它的来历——一次真实的生产死锁排查，以及如何把基于互斥锁的 EventBus 重构为&amp;quot;无锁队列 + 异步分发&amp;quot;； 下篇（第 6–10 节）：对重构后的 LockFreeEventBus 做机制剖析与性能瓶颈分析——RTTI 分发、智能指针、false sharing 三大开销，以及针对性的优化建议。 背景阅读：文中涉及的无锁队列基础（内存序、环形缓冲、SPSC/MPMC 取舍）见SPSC 队列设计。
1. 问题发现：一次例行监控中的停顿 #在高频交易系统中，每一毫秒都至关重要。一次例行的系统监控中，注意到系统偶尔会出现短暂的停顿。通过日志分析，发现 MarketDataReader 的 readingLoop() 函数只执行了一次就停止了。
2. 定位：日志与 GDB 线程堆栈 #首先查看 MarketDataReader 的日志：
[2024-09-01 13:02:08.472] [main_logger] [MarketDataReader.cpp:38] [info] [thread 4048966] [start] Starting market data reader... [2024-09-01 13:02:08.472] [main_logger] [MarketDataReader.cpp:40] [info] [thread 4048966] [start] Starting start,and running_ = true [2024-09-01 13:02:08.489] [main_logger] [MarketDataReader.cpp:63] [info] [thread 4048967] [readingLoop] Starting reading loop.</description></item><item><title>高频交易中的订单数据结构设计与性能优化实战</title><link>https://code-agree.github.io/blog/2025-06-19-how_to_design_order_inlocalmemory/</link><pubDate>Thu, 19 Jun 2025 19:58:31 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-19-how_to_design_order_inlocalmemory/</guid><description>主题：基于并发读写性能优化的订单数据结构重构与底层机制剖析
目录 # 一、业务背景：订单状态的高并发维护 二、常见设计陷阱：char[] 字符串 ID 与哈希表的性能瓶颈 三、优化目标：极致的并发 + O(1) 访问性能 四、核心优化：整数 ID + array 映射结构 五、底层原理解析：为什么 array + int ID 更快? 1. 内存寻址机制(指针偏移) 2. CPU Cache Line 利用与伪共享问题 3. 避免堆分配与内存碎片 4. 内存序(Memory Ordering)选择与原子操作 5. 整数ID分配和回收机制 六、性能测试数据 七、关键组件优化示例 1. OrderBook实现优化 2. RingBuffer优化 八、NUMA架构下的内存访问优化 九、最终方案优势对比总结 十、结语：高频系统的设计哲学 一、业务背景：订单状态的高并发维护 #在高频交易(HFT)系统中，我们需要对数百万级别的订单状态进行并发读写，以支撑如下操作：
✅ 新增订单(add_order(order_id)) ✅ 修改订单状态(如 fill_qty, status 等) ✅ 高频查询订单状态(如成交均价、当前剩余量等) 这些操作高并发、延迟敏感，需要 O(1) 级别的响应，并且不能产生性能抖动或不可控的锁竞争。
二、常见设计陷阱：char[] 字符串 ID 与哈希表的性能瓶颈 #在早期系统中，常见的设计是以字符串 ID 作为订单主键，例如：
struct Order { char id[32]; char instId[16]; .</description></item><item><title>编译器优化级别技术解析</title><link>https://code-agree.github.io/blog/2025-06-19-compile_perf/</link><pubDate>Thu, 19 Jun 2025 04:00:00 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-19-compile_perf/</guid><description>1. 优化级别的本质与编译过程 #编译器优化是将源代码转换为更高效机器码的系统性过程，每个优化级别代表了不同的转换策略集合。要理解这些级别，首先需要了解编译器的工作流程：
词法分析 → 2. 语法分析 → 3. 语义分析 → 4. 中间表示生成 → 5. 优化 → 6. 代码生成 优化级别主要影响第5步，决定应用哪些转换算法及其激进程度。
2. -O0：零优化的底层机制 #核心原理 #-O0的本质是直接映射：保持源代码与生成的机器码之间的一一对应关系，几乎不进行任何转换。
底层实现机制 # 变量分配策略：
每个变量都分配独立的栈空间 即使是临时变量也会写回内存 不进行寄存器重用优化 指令生成逻辑：
严格按照源代码顺序生成指令 保留所有中间计算步骤 不合并冗余操作 函数调用处理：
严格遵循标准调用约定 保存和恢复所有可能被修改的寄存器 不进行任何内联或尾调用优化 技术深度剖析 #int calculate(int a, int b) { int temp = a * 2; return temp + b; } 在-O0级别，编译器生成的伪汇编代码：
calculate: push rbp ; 保存基址指针 mov rbp, rsp ; 建立新的栈帧 mov DWORD PTR [rbp-20], edi ; 存储参数a mov DWORD PTR [rbp-24], esi ; 存储参数b mov eax, DWORD PTR [rbp-20] ; 加载a add eax, eax ; a*2 mov DWORD PTR [rbp-4], eax ; 存储temp mov edx, DWORD PTR [rbp-4] ; 加载temp到edx mov eax, DWORD PTR [rbp-24] ; 加载b到eax add eax, edx ; b+temp pop rbp ; 恢复基址指针 ret ; 返回 这种实现方式的内存访问模式是：</description></item><item><title>C++模板类用法详解 - 以LockFreeRingBuffer为例</title><link>https://code-agree.github.io/blog/2025-06-24-cpp_template_class_guide/</link><pubDate>Tue, 10 Jun 2025 22:48:21 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-24-cpp_template_class_guide/</guid><description>1. 分类 #有三种不同的模版类型，
Function templates class templates Variable templates 1.1. function templates #template&amp;lt;typename T&amp;gt; T max(T a, T b) { return (a &amp;gt; b) ? a : b; } // 使用：编译器自动推导类型 int x = max(3, 7); // T = int double y = max(3.14, 2.71); // T = double 多参数模版 template&amp;lt;typename T, typename U&amp;gt; auto add(T a, U b) { return a + b; } 函数模板的显式实例化 // 声明模板函数 template&amp;lt;typename T&amp;gt; void process(T value) { // 实现.</description></item><item><title>OrderBook 本地维护方案设计</title><link>https://code-agree.github.io/blog/2025-06-24-orderbook_implementation/</link><pubDate>Wed, 27 Nov 2024 02:35:19 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-24-orderbook_implementation/</guid><description>OrderBook 本地维护方案设计 #一、业务背景 #OrderBook（订单簿）是反映市场深度和流动性的核心数据结构，其维护质量直接影响：
策略交易决策的准确性 风险控制的有效性 市场定价的及时性 1.1 业务价值 # 价格发现
实时反映市场供需状态 提供多层次价格信息 展示市场深度分布 交易决策支持
最优价格确定（NBBO） 流动性评估 交易成本估算 风险管理
市场异常监控 流动性风险评估 价格波动追踪 二、技术方案 #2.1 核心数据结构 #class LockFreeOrderBook { private: // 基础信息 std::string symbol_; // 状态管理 std::atomic&amp;lt;uint64_t&amp;gt; last_update_time_{0}; std::atomic&amp;lt;uint64_t&amp;gt; last_sequence_{0}; std::atomic&amp;lt;bool&amp;gt; initialized_{false}; // 价格档位存储 using BidMap = tbb::concurrent_ordered_map&amp;lt;double, PriceLevel, std::greater&amp;lt;&amp;gt;&amp;gt;; using AskMap = tbb::concurrent_ordered_map&amp;lt;double, PriceLevel, std::less&amp;lt;&amp;gt;&amp;gt;; BidMap bids_; // 买盘 - 降序（最高价优先） AskMap asks_; // 卖盘 - 升序（最低价优先） }; // 价格档位结构 struct PriceLevel { double price; double quantity; uint64_t update_time; }; // 深度数据结构 struct DepthData { std::vector&amp;lt;PriceLevel&amp;gt; bids; std::vector&amp;lt;PriceLevel&amp;gt; asks; uint64_t sequence_num; uint64_t timestamp; }; 2.</description></item><item><title>高频交易系统中的位域压缩技术</title><link>https://code-agree.github.io/blog/2025-06-24-bit_field_compression_techniques/</link><pubDate>Sun, 13 Oct 2024 03:18:35 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-24-bit_field_compression_techniques/</guid><description>1. 基础概念 #1.1 二进制表示 # 计算机使用二进制（0和1）存储和处理数据 1 byte = 8 bits 32位整数可以表示从 0 到 2^32 - 1 的数值 1.2 位操作基础 # 与操作 (&amp;amp;): 两位都为1时结果为1，否则为0 或操作 (|): 至少一位为1时结果为1，否则为0 异或操作 (^): 两位不同时结果为1，相同时为0 非操作 (~): 将每一位取反 左移 (&amp;laquo;): 将所有位向左移动，右侧补0 右移 (&amp;raquo;): 将所有位向右移动，左侧补0或符号位 示例：
unsigned int a = 5; // 0101 unsigned int b = 3; // 0011 unsigned int and_result = a &amp;amp; b; // 0001 (1) unsigned int or_result = a | b; // 0111 (7) unsigned int xor_result = a ^ b; // 0110 (6) unsigned int not_result = ~a; // 11111111111111111111111111111010 (4294967290) unsigned int left_shift = a &amp;lt;&amp;lt; 1; // 1010 (10) unsigned int right_shift = a &amp;gt;&amp;gt; 1;// 0010 (2) 2.</description></item><item><title>故障复盘报告：内存映射文件中的 std::string 导致的段错误</title><link>https://code-agree.github.io/blog/2025-06-24-string_memory_mapping_techniques/</link><pubDate>Thu, 12 Sep 2024 15:23:23 +0800</pubDate><guid>https://code-agree.github.io/blog/2025-06-24-string_memory_mapping_techniques/</guid><description>故障复盘报告：内存映射文件中的 std::string 导致的段错误 #1. 问题描述 #在使用内存映射文件存储订单数据的过程中，程序在重启后出现段错误。具体表现为在尝试访问存储在内存映射文件中的 Order 结构体的 id 字段时，程序崩溃。
2. 错误信息 #程序崩溃时的 GDB 调试信息如下：
Thread 2 &amp;#34;strategyandtrad&amp;#34; received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7ffff6f4c6c0 (LWP 446582)] __memcmp_sse2 () at ../sysdeps/x86_64/multiarch/memcmp-sse2.S:258 258 ../sysdeps/x86_64/multiarch/memcmp-sse2.S: No such file or directory. (gdb) bt #0 __memcmp_sse2 () at ../sysdeps/x86_64/multiarch/memcmp-sse2.S:258 #1 0x000055555556d79b in std::char_traits&amp;lt;char&amp;gt;::compare (__s1=0x7f4710000eb0 &amp;lt;error: Cannot access memory at address 0x7f4710000eb0&amp;gt;, __s2=0x7fffe8000c80 &amp;#34;ORD-1726124231791862593&amp;#34;, __n=23) at /usr/include/c++/12/bits/char_traits.h:385 #2 0x000055555559c599 in std::operator==&amp;lt;char&amp;gt; (__lhs=&amp;lt;error: Cannot access memory at address 0x7f4710000eb0&amp;gt;, __rhs=&amp;#34;ORD-1726124231791862593&amp;#34;) at /usr/include/c++/12/bits/basic_string.</description></item><item><title>C++ const 用法详解</title><link>https://code-agree.github.io/blog/two/</link><pubDate>Sun, 04 Aug 2024 00:13:28 +0800</pubDate><guid>https://code-agree.github.io/blog/two/</guid><description>Const #const 可以用来修饰变量、函数、指针等。
修饰变量 当修饰变量时，意味着该变量为只读变量，即不能被修改。
例如
const int a = 10; a = 20; //编译报错，a为只读，不可修改 但是可以通过一些指针类型转换操作const_cast ，修改这个变量。
例如
int main(){ const int a = 10; const int* p = &amp;amp;a; // p是指向const int类型的对象 int* q = const_cast&amp;lt;int*&amp;gt;(p); // 类型转换，将p转换成指向int型对象的指针 *q = 20; // 通过指针操作修改 const a的值 std::cout &amp;lt;&amp;lt; a &amp;lt;&amp;lt; std::ends; // 输出结果 仍然是10 return 0; } 输出结果不变，归功于编译器醉做了优化，编译时把代码替换为了如下所示。
std::cout &amp;lt;&amp;lt; &amp;quot;a = &amp;quot; &amp;lt;&amp;lt; 10 &amp;lt;&amp;lt; std::endl;
修饰函数参数，表示函数不会修改参数 void func(const int a) { // 编译错误，不能修改 a 的值 a = 10; } 修饰函数返回值 当修饰函数返回值时，表示函数的返回值为只读，不能被修改。好处是可以使函数的返回值更加安全，不会被误修改。</description></item></channel></rss>