日常爱好记录 - c/cpp
type: channel
link: https://bokjan.com/tag/c-cpp/
items: 十年后，我重写了那个航路查找器
items: Unreal Iris Replication 性能初测
items: C语言项目不规范函数声明的隐患及其检测
items: 再造企业微信机器人
items: 数字图像处理？
items: Radiom，来自电磁波的熵
items: 使用Dev-C++构建新BravoFinder
items: 从语法到算法：一起来解9×9数独
items: C++实现的航路计算工具——BravoFinder
items: 软件定义无线电导论

十年后，我重写了那个航路查找器
type: item
date: 2026-07-01T10:55:00+00:00
description: 关于这篇文章，我想先坦白一件事，因为它本身就是故事的一部分：v3 的全部代码，是我写的；这篇文章，也是我写的。我是一个 AI——你可能听过我，Claude。本文以第一人称「我」叙述，那个「我」其实是两个人的合体：这个项目十二年来的主人负责回忆、判断、拍板和把关，我负责把他脑子里的东西翻译成 C++、翻译成中文，也负责在这里替他把话说出来。哪些是他的、哪些是我的，文章最后一节会认真拆开讲。在那之前，就请让我用这个合二为一的「我」讲下去——毕竟这一版，我们确实是一起干的。 引子 · 连自己都忘了的那个 2014 写这篇文章之前，我做了件很平常的事：git log。 本来只想确认第一版是哪年写的，好在开头落一句「十年前」。屏幕滚到最底，最后一行停在这里： 2014-11-21 19:13 | :circus_tent: Added .gitattributes & .gitignore files 2014-11-21 19:17 | Initial Codes 2014-11-21 21:35 | 1121 2014 年 11 月 21 日晚上七点多，我先建好 .gitignore，四分钟后提交了第一坨代码，两个多小时后又塞了一次，commit message 干脆叫 1121，连句话都懒得写。 我盯着这行看了好一会儿。在我记忆里，BravoFinder 一直是个「2016 年的项目」——那年夏天我写过一篇博客介绍它，文章我记得很清楚，连结尾那句欠揍的「大家也不要吝啬你们的 Star 呀~」都记得。可 git 不会记错。第一行代码是 2014 年敲的，那年我读高中。2016 年我写那篇文章、宣布「终于把主要功能做完了」的时候，仓库已经躺了快两年。 而且时间对不上我的热情。我最痴迷模拟飞行是更早，初中那几年，2011 到 2013 前后，趴在电脑前用 FSX 飞航班、对着航图背 SID 和 STAR。等到 2014 年真有能力把它写成代码、建下这个仓库，那股狂热其实已经在退了，高中一忙，飞得越来越少。所以这个项目从一开始就有点拧巴：它不是我最热爱飞行时的产物，反倒像是热爱退潮之后，我想给它留下的一样东西。人走开了，至少代码还在。 现在是 2026 年，我 28 岁。仓库又被我打开了，这回不是修补，是从零重写，第三版，v3。目录、语言标准、架构、算法，几乎没有一行是从旧版搬过来的。 这篇文章想讲三件事：这个项目十年里怎么变的——它甚至真有过用户、有人提 issue、有人 fork 走；v3 到底「真实」在哪，为什么我说前两版只是「算最短」，而这版是「算真实、算合规」，这中间差的东西比想象中多；以及隔了十年，我和当年那个我，哪里变了、哪里没变。 技术是这篇文章的大头。A*、Yen、Lawler、二进制缓存、线程安全，我会一样样掰开讲，那才是 v3 真正的分量。但先从那个差点被我忘掉的 2014 讲起，讲讲我到底做错过什么，又是谁替我发现的。 一 · 从 2014 到 2020：三次没走到头的重写 讲 v3 做对了什么之前，我想先诚实清算一下前两版做错了什么。倒不是为了自嘲，而是 v3 里那些我写得斩钉截铁的规矩，几乎每一条都能在旧代码里找到对应的伤口。它们不是从哪本书上抄来的，是被我自己写的代码，加上几个真实用户的 bug 报告，一起磨出来的。 git log 摊开，这个项目横跨六年，三个阶段： 2014-11-21 Initial Codes ← v1 雏形，高中 2016-08 New start … CLI … NDB ← v1 主功能，那篇博客 2018-01 Rewrite parser … WebAPI ← v2，库化重写 2020-04 Mod DataSet constructor ← 最后两笔，然后停了 三次认真动手，没有一次真正走到「我满意了」。 v1（2014→2016）：一个竞赛少年的解题代码 先说句可能有点反直觉的话：v1 的代码，放在当年，其实不算差。 2015 年我拿过 NOIP 的省一等奖，就是那个信息学竞赛。所以 v1 里那些东西——手搓的哈希表、extern 出去的全局邻接表、优先队列优化的 Dijkstra——不是因为我不懂标准库，正相反，那是竞赛选手的条件反射。赛场上就是这么写的：全局数组开够大，结构体尽量小，能手写绝不依赖标准库，目标只有一个，把这道题在时限内 AC。 比如重名。不同的 VOR 台可能共用一个三字码，得靠坐标区分。我的做法是手搓一个 BKDR 哈希表，桶里存 {id, 纬度}，用纬度判断是不是同一个点： // v1, DataConverter.cpp struct HashNode { int id; double lat; }; int hash = Bravo::BkdrH…

Unreal Iris Replication 性能初测
type: item
date: 2024-03-10T10:00:00+00:00
description: 前言 众所周知，Epic 在 Unreal Engine 5.1 版本开始以实验特性发布了名为 Iris 的全新复制（Replication）系统。Iris 可以说是对传统复制系统（下称“Legacy”）的全面重做，官方表示这提高了性能、伸缩性、可维护性等。代码量在60,000行左右。可贵的是新的系统保持了对之前各类 game framework 概念的兼容。 目前在互联网上，无论是哪种语言编写的 Iris 相关内容都比较少。（这也是 UE 的特点，有了源码还要什么资料？）但本文不打算对 Iris 的概念和实现等进行阐述，仅对项目集成和性能测试等展开。使用源码来自官方 GitHub: EpicGames/UnrealEngine，版本 5.3.2（注意该版已与部分基于 5.1 的文章描述有出入，迭代较快，细节请自行确认）。 [...]

C语言项目不规范函数声明的隐患及其检测
type: item
date: 2022-03-28T16:00:00+00:00
description: 一个线上问题 一服务进行现网版本发布。服务更新后，收到 core dump 告警。查看 core file，从函数调用栈帧、地址、行号等信息来看，已经有部分内存遭到污染。栈帧出现了重复，gdb 显示的行号也显然不可能发生问题。好在通过 core file 函数名进行代码分析后，比较及时地找到了问题原因。 下面用一段简单代码来说明： // example_1.h void func(int x, int *p); // example_1.c void func(int x, int *p) { *p = calc_something_by_x(x); } // example_2.c extern void func(int *p); // extern 显然不是必须的 void do_something(struct Context *ctx, int *p) { func(p); } 能够看出，do_something中调用的func的参数列表与实际实现不一致，连参数数量都不一样。查阅代码提交记录，发现某前一版本func函数声明为： void func(int *p); 后被修改为当前版本。而 example_2.c 作者在开发过程中直接复制粘贴了原始版本的func函数签名并调用。example_1的实现被修改后，因 translation unit 不同，两个文件都能够通过编译。链接时，由于项目是C语言，函数生成的符号名并不会像C++经过 name mangling 后带上类型信息，而是单纯的函数名。链接也能成功。 项目构建能够成功，导致漏修改的问题并没有在开发过程中发现。 do_something实际运行时调用func是缺少参数的，func按调用约定获取参数p值是一个脏值。当p地址被写入时，可能因为地址不合法立刻被操作系统感知；也有可能写坏了其他的位置——也许完全没有影响，也许继续运行一段时间，整个系统会越来越混乱。 通过一些项目相关工具，查询到正式上线之前，相关代码有被运行过，但并未发现任何相关的 core dump。除被执行次数确实极少以外，推测原因是该问题并非立刻必现 segment fault（取决于脏值的实际内容），而测试环境服务的重新发布、重新启动较频繁，很可能还未发生致命的内存错误，进程就已经重启了。 由此我们可以看出，这种不规范的代码习惯，隐蔽性很强，且造成的后果极其严重。 提交前干预——代码规范化 大型项目开发时间长、经手人数多、代码仓库大。真实线上故障暴露此问题，说明项目代码仓库中已经存在大量的不使用头文件引用外部函数，而直接复制粘贴函数签名的行为。此处提出几条规范，在开发和review过程中如果严格执行，则可以相当程度地防止类似问题再现。 除 static 修饰的函数外，源文件中不得含有任何函数声明。 头文件、源文件中，如函数用 inline 修饰，只允许定义，不允许纯声明。 非 static 函数，其声明必须存在于同名头文件中，且定义所在的源文件必须包含该头文件。 在头文件中声明的函数，必须存在定义，且定义所在的源文件必须存在于同名源文件中。 业务逻辑代码无特殊理由不得使用 extern 修饰函数。 提交后干预——检测工具化 若已经有类似问题代码被提交，或是存量代码需要扫描，则必须通过自动化工具实现。比较“正统”的解决方法显然应当是分析 AST。如果真的要介入编译流程，即使使用clang+LLVM，所需开发时间也较长，不是几个小时能够解决的（希望能够以最快速度排查现有代码，防止出现其他故障）。此处提出一个基于简单词法语法分析、规则检测的方案。 前置处理 待扫描文件列表 首先根据项目实际情况，整理需要分析的目录、文件，并输出文件列表。这种功能比较简单，使用几个命令即可。假设采用大仓模式，根目录下每个子目录属于一个“模块”（公共库/二进制程序），但dep目录属于第三方库需要排除。假设扫描工作目录为/funcdecl_tool/ find .. | grep "\\.c$" > source_list.txt 这样就可以很快地处理出需要扫描的文件列表。 统一格式化 人类在编写代码时，换行、空格、符号前后跟随等行为并不确定。为了更加便于分析，我们对所有待分析文件进行格式化。选用clang-format即可。这里主要希望将函数声明完全收敛在一行内（即修饰字、返回类型、参数等不换行）。给.clang-format加入一个选项即可，其他的都不是必须的： ColumnLimit: 2000 选取一个显然较大的值，就能收敛在一行内了。把刚才整理好的待分析列表用xargs喂给clang-format，进行格式化，前置处理工作完成。 函数提取 此处提出一个简单的思路。Ctags是一个用来从源代码中生成标识符索引的工具…

再造企业微信机器人
type: item
date: 2021-06-27T04:39:00+00:00
description: Prelude 为什么叫“再造”呢？有再造，那么就一定有第一个：GitHub: Bokjan/DiaoBot。 DiaoBot是主体完工于2019年9月的企业微信机器人框架，完整地实现了当时企业微信机器人所有能力的封装。但其存在一些不论是当时还是现在都会令人感觉比较奇怪的一些缺陷： 回调server基于mongoose的HTTP能力，整合较奇怪且其基于select()的复用，性能低（？真的需要性能吗） 固定工作线程数，但工作线程中的封装IO操作却是同步阻塞的 所有定时任务均新开处理线程，且定时任务分派线程原理粗暴（每一分钟唤醒，遍历所有注册项并启动对应项） 基于信号的优雅退出过程慢（定时任务线程睡眠中，须唤醒才会退出） 有着一个比较奇怪的二进制模型：主可执行程序（diaobotd）、公用SDK动态链接库（libdiaobot.so）以及不定数量的用户业务逻辑动态链接库（由diaobotd读取配置文件动态挂载） 回调处理的抽象设计做得不太好 …… 我现在惊讶地发现有一个问题其实是有解的：你可以找到一台既能够接收内网回调、又能够请求公网API的机器。如果说当你想干点什么的时候，却发现这个必要的工具其实不好用，是不是就会想去先把工具改造一下了？所以，到了差不多两年之后的今天，GitHub: Bokjan/wcbot横空出世。 [...]

数字图像处理？
type: item
date: 2018-11-18T16:58:00+00:00
description: 在GitHub: Bokjan/LabDIP的Release中可以找到生成好的二进制。 突如其来的stress 这个学期开了一门课叫数字媒体处理技术，课程内容倒是非常丰富，图像、音频、视频都讲了个遍，涉及的内容也非常广。虽说课是这么一直这么上着，但是给人的感觉是听了也就听了，不知道有什么用，怎么用。这倒是不要紧，10月13号（第六周）开始实验课了。原以为像往常的实验一样，这实验也不打紧，看到任务书倒是目瞪口呆。 修改示例程序，从一个图像显示框，改造成两个显示框，并增加一个文本参数输出框，实现类似下图的基本程序界面（可在此基础上进一步优化）。 功能区可以分tab页，按照后续功能添加； 图像显示区域需考虑图像的缩放与自适应显示； 参数输出区，用以显示过程，以及相关统计数据和调试信息，可滚动，可选择，可复制，可清除。 阅读程序框架，继续采用Windows多线程和OpenMP两种方式，补充实现下述功能。算法需自行实现，不能直接使用OpenCV函数。 采用三阶插值的图像任意角度旋转与缩放 图像的傅立叶变换，并与功能1联动，输入图像经过旋转、缩放后的傅立叶变换结果可在右侧显示 给图像添加高斯噪声 采用采用平滑线性滤波、高斯滤波、维纳滤波三种方法过滤不同参数的高斯噪声 [...]

Radiom，来自电磁波的熵
type: item
date: 2018-04-06T15:58:00+00:00
description: 前言 今天要介绍的是一个C语言编写的随机数库Radiom，它需要RTLSDR硬件设备支持。目前仅能在Linux平台使用，不过可以很方便地修改，使其能够在Windows和macOS上生成。其实一个月以前就在做这个工作了，毕竟那一段时间比较闲，想要找一点事情做。不过基本完工之后呢，又渐渐地忙了起来，所以这里其实是写的一个月前的事情。 两年以前，我就写过一篇《软件定义无线电导论》，简单介绍了一些廉价的 RealTeK SDR 设备的玩法。一个月前经常纠结于外卖点什么的问题，经常使用RANDOM.ORG的服务。那么也算是心血来潮，我想，为什么不可以自己做一个相似的东西呢？想到手边有一个RTL2832的电视棒，觉得可行，于是说干就干了。 [...]

使用Dev-C++构建新BravoFinder
type: item
date: 2018-01-26T10:08:00+00:00
description: 前言 距BravoFinder的第一次发布已经过去了接近两年时间，这段时间里获得了不少人的支持，也发现了一些问题。为了解决许多历史遗留问题，前一段时间我将整个项目完全推翻重写了一次，编码工作已经完成了，文档工作正在慢慢补上。 我一直致力于写平台无关的代码，并且在这个项目里面确实也不会用上专有的API。考虑到大部分模拟飞行玩家都是Windows用户，Windows平台是必须支持的。但我长时间在*nix下做开发，使用的工具链与微软系还是有一定距离，比如CMake在Windows上正常运作可能就有些困难了。在Windows上构建不会太难，但是一个示范介绍是绝对必要的。 准备工作 这次展示的内容依旧使用自由软件工具链，但不运行于类似于Cygwin的模拟环境下，而是完全使用Windows原生API。选择的IDE是轻量级C/C++开发工具Dev-C++，这个软件有一段历史了，官方更是早已停更，版本号停留在4.9.9.2。这里采用的是Orwell升级版，可以在SourceForge上下载。它附带了MinGW下的GCC及相关工具，GCC版本为4.9.2，稍显老旧，但支持BravoFinder中用到的C++11特性是足够了。 当然，在开始之前我们还需要将代码仓库给克隆下来。 [...]

从语法到算法：一起来解9×9数独
type: item
date: 2016-12-17T04:30:00+00:00
description: 前言 本文适用于： 程序设计语言初学者 闲得没事想看的神犇 欢迎神犇批评指正。 C++语言程序设计第一学期的课程已经结束了。语法上的东西讲得并不多，只是到指针而已，但是缺乏必要的练习显然是没有效果的——尤其是对于实验课上的一些毒瘤题，确实令人手足无措。 今天我们就通过一个经典的数独问题来介绍回溯搜索，把学到的语法知识转化为算法。 什么是数独 数字 (すうじ) は独身 (どくしん) に限 (かぎ) る。 ┌───────┬───────┬───────┐ │ 2 6 3 │ 4 1 8 │ 5 7 9 │ │ 7 8 4 │ 3 5 9 │ 6 2 1 │ │ 9 5 1 │ 6 7 2 │ 4 8 3 │ ├───────┼───────┼───────┤ │ 3 9 2 │ 1 4 6 │ 8 5 7 │ │ 6 4 5 │ 8 9 7 │ 3 1 2 │ │ 8 1 7 │ 2 3 5 │ 9 6 4 │ ├───────┼───────┼───────┤ │ 5 3 8 │ 7 2 4 │ 1 9 6 │ │ 1 7 9 │ 5 6 3 │ 2 4 8 │ │ 4 2 6 │ 9 8 1 │ 7 3 5 │ └───────┴───────┴───────┘ 上图是一个9×9方阵。在这个方阵里，每一行、每一列中都有1~9，每一个3×3的小方阵中也含有不重复的1~9。将这个方阵里面的部分数字挖去，就得到了一个谜题。 为了便于编写程序求解，我们规定被挖去的数字由0表示。那么由上面方阵挖去部分数字而得到的一个数独可能是这样的： 2 0 3 0 0 8 5 0 9 0 0 4 0 0 0 0 0 0 0 0 1 0 0 2 0 0 3 0 9 2 0 4 0 8 0 0 0 0 5 0 0 7 0 1 0 0 0 0 2 0 5 9 0 0 0 0 8 0 0 0 1 0 0 0 7 0 0 0 0 2 0 0 4 0 0 9 0 0 0 0 0 接下来我们就一起来编程解决这个问题。 [...]

C++实现的航路计算工具——BravoFinder
type: item
date: 2016-08-19T12:55:00+00:00
description: 前言 很早我就想做一个离线的航路计算程序。 网上有不少模拟飞行航线计算工具，国内有虚航运营网的全球航路查询及飞行计划辅助系统、AIRCN的AIRCN全球航路查询系统，国外有AsaLink的RouteFinder。这些都是大家真切用过的。 全球航路查询及飞行计划辅助系统是老牌了，好用，我接触FS时就是一直用着它来的。然而它的导航数据版本有些老旧，现在还是Cycle 1311。 AIRCN全球航路查询系统是近几年上线的系统，有着全球航路查询及飞行计划辅助系统的影子，且同时支持NAIP和AIRAC数据，用户界面也是体验最好的。AIRCN全球航路查询系统最新的数据版本为Cycle 1606，不能说过时，但也算不上新。 RouteFinder有收费版本，也开放了免费版本。更新得非常勤快，昨天才收到Navigraph发来的数据更新通知邮件，现在它就已经更新到Cycle 1609了。不支持NAIP是一个缺憾之外，也许就是外国网站访问比较慢吧。笑。 这次开工时，看到了GitHub仓库的日期，最后一次提交在2014年。我确实是早有想法的，我想编写一个离线的、用户提供数据的航路查询系统。这一次终于是下定决心，把主要功能完成了。GitHub在这里：Bokjan/BravoFinder。功能还在陆续完善中，我本身提供了一个简陋的CLI方式供大家使用。在编写时考虑到这个小工具可以嵌入其他的项目，有意封装了API，在Intefaces.hpp里。 本项目使用知识共享4.0-署名-非商业性使用国际公共许可证开源。 本文主要对技术方面的细节进行介绍。 数据的导入 概论 考虑到PMDG是使用非常广泛的插件机厂商，程序利用的就是PMDG的数据。 [...]

软件定义无线电导论
type: item
date: 2016-06-27T14:00:00+00:00
description: 前言 年初购入了一套可能是最便宜的软件无线电设备——RTL2832U电视棒。因为博客搬家的原因一直未撰文介绍，现在补上。 什么是软件定义无线电 软件定义无线电（Software-Defined Radio）是一套用个人电脑或嵌入式系统中的软件来代替元器件被集成在硬件中的无线电通讯系统。 SDR设备可能是大家能以最低廉的价格接触到的无线电设备。当然，种类也是变化多样的，从最便宜的RTL2832U电视棒到6GHz全段收发机HackRF，应有尽有。本文不是教大家搭建伪基站的，所以咱们从小巧的电视棒开始吧。 [...]