日常爱好记录
type: channel
link: https://bokjan.com/
items: 十年后，我重写了那个航路查找器
items: Unreal Iris Replication 性能初测
items: 2024新年初访北海道
items: 以最不折腾的方式取得日本业余无线电资格
items: 在日本增驾摩托车
items: 我如何通过 BJT 商务日语能力考试
items: Replication Graph 从入门到入土
items: B类业余电台操作技能考试备考笔记
items: C语言项目不规范函数声明的隐患及其检测
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 的文章描述有出入，迭代较快，细节请自行确认）。 [...]

2024新年初访北海道
type: item
date: 2024-01-08T12:40:00+00:00
description: 辞旧迎新 ⚪︎2023年12月31日晚，東武池 (いけ) 袋 (ぶくろ) 駅西口（北）附近（東京都豊島区西池袋1-25-1）。 神奇的2023在一片欢声笑语中落幕了。这一年，我来到了加入公司后的第三个工作部门；更加地，阔别了学习工作了7年的广东，搬到东京，成为了一位外国人住民。12月的最后几天里，有幸和不少来访的朋友共眺夜景，在りんかい線国際展示場駅体验人潮涌动，来到「宇宙中心」東武池袋駅西口（北）感受家乡的温暖。新年钟声敲响，随着明 (めい) 治 (じ) 神 (じん) 宮 (ぐ) 的人潮学会了初詣 (はつもう) で；手持都区内パス，来了一把特种兵打卡：代々木 (よよぎ) 、御 (お) 茶 (ちゃ) ノ水 (みず) （三线交汇！）、秋 (あき) 葉 (は) 原 (ばら) 、上 (うえ) 野 (の) 、東京駅、皇居、銀座、新橋 (しんばし) 、高輪 (たかなわ) ゲートウェイ…… 就这样，2024年1月2日的早晨，我已经坐在羽 (はね) 田 (だ) 空港的航站楼里。作为一个多次组织人均千元以内五天以上活动的穷游大师，即将迎来的是可能第一次不那么穷的游——毕竟往返的机票都超过这个数了。 [...]

以最不折腾的方式取得日本业余无线电资格
type: item
date: 2023-12-16T09:55:00+00:00
description: 前言 之前在中国考了A、B类业余电台操作证书、也设了台。除了每年的考试场次比较有限，一切都非常简单，也基本上不需要花钱。今天来介绍一下在日本如何取得各类许可，合法地开展业余无线电活动。除了文中已经进行的比较外，其他未提及部分大家可以自行与中国的情况对比。 当然，与中国相同，需要2份许可：跟人的操作许可和跟机器的电台许可。在日语中分别称作無線従事者免許証和無線局免許状。也是同样地，需要持有無線従事者免許証之后才能设台（開局）。中国的主管机关是工业和信息化部，日本的主管机关是総務省。 [...]

在日本增驾摩托车
type: item
date: 2023-12-04T08:50:00+00:00
description: 前言 之前9月时做了外国免許切り替え。在鮫洲这种人巨多的考场，竟然只花了21天就完成从申请到考试（打电话提前）、免許交付。 由于在中国时基本都住在禁摩的城市，也没有动力去取得中国的D或E驾驶证。于是乎踏上了在日本的增驾之路。为了少给自己找麻烦，我选择了指定校——只需要在本校完成卒業検定，便可直接拿着证明去運転免許試験場做併記了（免除学科、技能考试）。已持有普通一種的情况下，学习普通自動二輪車MT（排量400cc以内，手动挡）需要参加19节技能教习和1节学科教习。下面会比较详细地说明具体的上课流程，可以来与中国对比一下异同。 [...]

我如何通过 BJT 商务日语能力考试
type: item
date: 2023-03-24T16:00:00+00:00
description: 前言 首先标题是不准确的：BJT 并没有是否通过的说法。每个人的“通过”，自然都是各取所需，达到自己需要的分数或等级就好。 作为一个日语水平在 N6 级别徘徊了十几年的人，直到 2022 年年底才开始系统性地学习日语。纯玩票性质的学习，自然是没有任何功利性、也没有任何紧迫性。选择了学校文法体系的教材（上海外语教育出版社《新编日语》）慢慢学。原计划是在这种不紧不慢的状态下 23H1 考 N3 玩玩，23H2 水过 N2 即可。然而计划赶不上变化，因为某些原因我急迫地需要一份语言资质，但一年两度的 JLPT 实在来不及。到 2023年2月月底意识到法務省承认商务日语能力考试（ビジネス日本語能力テスト，下称BJT），BJT 400分等同JLPT N2，且BJT由PearsonVUE组织纯机考，随时开考。虽然价格贵了不少，但好像也别无选择。 本文包含以下内容：BJT概况、基础知识建议、BJT备考建议、考试流程等。本文所有内容均以达成BJT J2（420分）为目标而撰写；关于语法的概念均使用学校文法表达。其中基础知识部分，应当适用于任何情况而不局限于商务日语。 [...]

Replication Graph 从入门到入土
type: item
date: 2022-10-23T06:06:00+00:00
description: 前言 Replication Graph 作为官方插件在 UE 4.21 版本发布。在学习使用过程中，也尝试上网查找过许多相关资料，没有发现特别详细的介绍。其实对于 Unreal 开发来说，很多事情的确不需要人家专门来长篇大论给一篇文章看——毕竟代码都给你了，有什么问题自己看看不就得了么？所以，上网找不到资料，只能说是吃了知识的亏，不够熟悉或者看不懂代码，那确实是自己的问题。 本文尝试简单介绍 UE 的 Actor 复制相关的内容，结合一些实际使用上的问题，希望能对完全没接触过的读者提供一些小帮助。笔者目前接触 Unreal 还只有三四个月时间，了解不多，才疏学浅，若是有谬误之处还请包涵与指正。 [...]

B类业余电台操作技能考试备考笔记
type: item
date: 2022-05-22T08:27:00+00:00
description: 前言 年初开始就一直在筹备 B 证的事情。受疫情影响，好几场考试都没能如期举行。直到今天，5 月，终于在惠州市工信局考完了。文章内容应该是 3 月左右边学边写的，等到正式考试过后验证了效果，再发布出来。 B 类与 A 类相比，题库容量更大，有 400 多道新题。备考的话，刷题加上死记硬背是个好办法，但是不少知识点如果能够知其所以然就更好了。这里对于部分知识点进行一下总结——不包括大部分 A 类的旧内容、不包括法律法规、不包括过于零散和简单的问题。 参考题库版本为 v20211022。 [...]

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-09-16T15:52:00+00:00
description: Disclaimer: 本文纯属抄袭给我也整一个——《中华人民共和国机动车驾驶证》。 三个月没更新博客了，趁此机会除除草，记录一下我顺利的拿执照历程。 写在前面 当年高考之后就想着得考个操作证以备不时之需了，但是考虑到家里的驾校不规范现象还是太多，而且练车不方便，时间也是个棘手问题，就一直拖到了大学开学。低年级的时候怕没时间就继续拖，结果发现越到后面越忙，最终大四上学期在深圳实习的时候，抽时间回广州报了个名，想着能在毕业前拿到驾照就好，没想到事与愿违，世风日下，世事难料…… 准备工作 2021年07月份，深圳市业余无线电协会发布了报名通知，周二在公司的时候就顺便搞搞报名的事情。打开通知之后先是要照相做照片，我说我自己带了电子版的PS好的照片，我就拿着我的网盘打开图片假装在 PS 里面操作了一下，然后报名通知对着我带的P好的照片说，你这个没用，我说我这个有用，我这是之前按照工卡照片要求的标准尺寸P的，传统照片是讲究标准的，诶……我一说，我啪就站起来了，很快啊！果然报名照片的尺寸要求不一样，我上来就是一个调整裁剪，也不用打印，直接放到报名邮件附件里面就可以了。 [...]