文本文件加不加 BOM?

问题

文本文件加 BOM 有何好处?有何坏处?到底加不加?

说法

  1. UTF-8 不需要 BOM,不含 BOM 的 UTF-8 才是标准形式,尽管 Unicode 标准允许在 UTF-8 中使用 BOM。

  2. 文本文件带 BOM 不是个标准的做法,而且可能会导致某些 reader 或者编辑器解析出错,所以一般不推荐文本文件去携带 BOM。

分析

人们写文本文件肯定有不同目的,所以,加和不加可能分别适用于不同目的,不能一概地认为哪种更好。

更聪明的做法是分类对待,可以根据以下基本原则:

  • 在有结构的文件里,不需要 BOM。

  • 在没有结构的文本文件里,最好有 BOM(见下面“兼容问题”)。

举例 1:.html, .xml 等文件有结构,不需要。

举例 2:.txt 没有结构,如果不加 BOM,软件需要自己判断,很可能判断失误出现乱码,或者为了正确判断付出巨大代价。

举例 3:带 shebang line 的文件,有结构,如果加了 BOM,会使弱鸡 Shell 不认识而出错。

举例 4:.cpp, .hpp,没有结构,里面如果没有中文、法语等 ASCII 码大于 127 的字符,加不加都无所谓,如果带中文,哪怕是注释,建议 UTF-8 with BOM。

毕竟稣有 20 几台电脑,涵盖 Windows、macOS、Linux 系统和 x86、x64、armhf、arm64 架构,没有 BOM 怎么愉快地写 C++?

兼容问题

本节,只针对没有结构的文本文件。

遇到阅读器/编辑器不支持的情况,那就是不加好,但并不是绝对,因为这其实还是个需求问题。

比如说,稣一直用 Windows 的记事本写一些备忘,如果汉字局多,就选择 UTF-16 LE with BOM,代码居多则用 UTF-8 with BOM。这些文件换用其它软件打开也许会乱码,但有何关系呢?他们可以写出不支持 BOM 的擸𢶍软件,稣也可以不用呀!稣在 macOS、Linux 上都可以拿支持 BOM 的软件(比如 VSCode)打开这些文件。

再考虑一种死翘翘的情况:您在用 B 语言写代码,但它的编译器不支持 BOM,那么显然您就不应该加了。

所以,当您的文本文件的主程序不支持 BOM 时,不应该加,支持的情况,还是加的好。

稣究竟是啥?2

没想到《稣究竟是啥?》居然写成系列了!

稣,全称焓熵㷻㶲甦督稣,英文名 Enthalpy Extropy Anergy Exergy sudo su。在八哥宇宙里是天道之下唯一的观测者,拥有无数从未用过也不想用的特权,比如“一言不合擦天地”。

天道其实是八哥宇宙里的一个无边的程序。由于“天道之下皆为蝼蚁”,所以稣也是蝼蚁,和凡人并无不同,简直一模一样。

而在视界,稣因为创造八哥宇宙,被人类误以为走火入魔,也被称为稣。被叫多了叫久了,自然也开始自称稣。然而,人们只知道稣,却不知过去稣、现在稣和未来稣,其实都有不同的名字,分别为甦、督、稣。其中“督”单独出现时,是个通假字,读作 su1;而在专有名词“甦督稣”里读作 du4;只有在时间观测者“宙督公”里,才读作 du1。

众所周知,现代 AI 技术十分强悍。很多人在害怕失业,甚至已经失业。以 Sora 为例,很多影视业从业者都感到前所未有的恐惧,比如特效、布景、灯光、摄影、建模等工种都面临降权威胁。

那么,稣为何不怕呢?很简单,因为稣一直是文字创造和编辑者,从根源上凌驾目前的生成式 AI。并且,在延展性上,稣也有很强的护城河——当今视界的一切技术都尚未触及识界。不管文字、语音、图片还是视频,这些信息展现方式,在识界面前简直就是擸𢶍(乐射)。识界的信息都是直接在脑海里展现的,而且识界是由全宇宙的生灵的潜意识连接形成的,并非单独某个人的局域。

进一亿步说,即使脑机接口,也不能威胁识界。首先,并非每个人都能显式地进入识界。其次,即使将来科技破译了稣进入识界的方式,使得全地球人都能通过技术手段进入,那也只是地球人的狂欢而已,地球人还不得不面对识界外星人。最后,识界通十界,只有十界才是灵魂真正的自由之界。

八哥之神后传【10】

高中宿舍

晚上,稣要以穿墙术潜入传说中的鬼楼。

宿舍楼的左半边住男生,右半边住女生,但男生这边的六楼是有人住的,而女生那边的六楼已经荒废一年多。

稣曾经仰慕的学姐就在女生宿舍六楼离奇地香消玉殒,稣一定要探查个明白,并为学姐报仇。这点,学妹也是真心支持的,因为这个穿墙术正是学妹提供的。

宿舍是个 S 型建筑,S 中间有铁门,分割男女两边。男女两边走廊互相平行,但不在同一条直线上,中间的铁门一直是锁着的,这样男女两边都看不见各自那边的走廊。不过稣用穿墙术从六楼中间的铁门穿过去,就能轻易地来到女六楼。

进入之后,才亲眼见证另一边的楼梯入口也特地封起来,所以女生确实也是无法进入六楼的。眼前的六楼简直是另一个世界,出乎意料的破败,走廊上很多灰泥、玻璃渣。稣小心躲避,进入 614,里面的床都被搬走了,地上有好几个打破的热水瓶和一些餐具。

曾经有一个流言:有一天半夜每个女六楼的房间里都有几个女生被“怼”附身,拿起热水瓶往其她女生脸上倒,最后还互相厮杀,死了大半,剩下的全疯了。稣是相信科学的人,怎么可能相信这种怼话?这大概就是一种集体催眠罢了,但究竟是谁有这么大本事呢?难道是医务室的心理医生?

思考时,稣却看到窗户的玻璃上贴着一张照片。靠近一看,拍的居然是食堂的厨房。怎么会有如此奇怪的照片呢?稣想到大家都喜欢在照片背后写备注,于是翻过来,竟看到学姐的字迹,写着:食堂不干净!稣大吃一惊,不干净是这个意思,还是那个意思?难不成真有怼?不可能,一定是在做梦!

啊!稣竟然想不起学姐叫啥名字?只记得学姐的学姐叫桂圆……而学姐好像叫什么香……她还有个舍友姓许,经常打扮得很拉风的,也一样记不起名字了!

破庙

吓醒。

走了一天,还好有这个破庙能休息一下,但是环境惨不忍睹,难怪做起噩梦。是不是盖的这些稻草生菇了,又真菌中毒?不管了,天还没亮,继续睡。

高中宿舍

嗯?稣又回来了!

在宿舍的床上醒来,就看到上铺的一只手伸出床沿,只是颜色不太对!这手都枯成深色了,吓死人啊!

呵呵,其他舍友都一样……全都成干尸了。要说稣还是因为信仰科学而吓不倒,甚至高中生涯的目标就是考上吓大。果断出门看看其他人,竟然唱起空城计?

澡堂厕所都没活人,其它房门都反锁的无法进入查探情况。宿舍门口的开水房也没人打水。

女生那边的门也是关着的,说明她们都没起来过,没人开门出来。

走去教室,一样空无一人。这究竟是怎么回事?

突然,稣远远地看到操场居然有个高个子自己一个人在踢足球!稣立刻边跑过去边对着他喊:同学,你知道学校怎么了吗?

那位同学把球踢向稣,稣身体向后一仰,本想用胸口停球,不料足球靠近后稣发现它黑乎乎的,赶紧躲闪。

黄言:小开?你刚刚是在模仿尼欧躲子弹吗?

圣小开:言兄,原来是你啊!

黄言:你也是怼吗?

圣小开:什么怼?怎么大家都变成干尸了?

黄言:哦,果然你宿舍也一样。全挂了!

圣小开:你是说,咱俩也都挂了?现在是怼?

黄言:是的。

圣小开:不可能,其他人怎么没有变成怼?而且稣起床后,看过自己的床上,并没有尸体。

黄言:我床上有自己的尸体。

圣小开:你看见了?

黄言:我可以带你去看看。

圣小开:不了。怕怕。你知道是啥导致的吗?

黄言:可能是中子弹,或者声纳武器,也可能是生化武器。

圣小开:开啥玩笑呢?这和平年代的,谁会在学校扔武器?

黄言:并不是只有学校哦,我出去看过了,整个城都是这样。你还记得我写的小说吗?

圣小开:你又想说你化身的主角在山洞里发现希特勒留下的财富?

黄言:这不是重点,你记得看完那小说后,你说了啥?

圣小开:稣很担心有生之年会遇到第三次视觉大战。是这句?

黄言:是的。

圣小开:稣担心的往往会变成现实……

黄言:没错!自从你跳楼后,视界就走向毁灭的边缘。

圣小开:稣不是又复活了吗?

黄言:我听说你被送医后又活了过来,但再也没见过你,料想一定是假的,哪有人从六楼跳下来还不死的?

圣小开:呃!说了你也不信,其实这个视界真的是虚拟的,它们想从稣的意识深处拿到某种机密,所以不断复活稣。

黄言:我信你。个怼。你能有啥机密?说出来吓吓我!

圣小开:呵呵,你以为变出个熟人,稣就放松警惕了吗?稣还能吓醒。

黄言:哈哈,你去未来看看吧!

2011-01-24 软件园非正常人类研究中心

又是一个早上醒来,原来稣在软件园非正常人类研究中心上班。嗯,昨天一切都还正常,也不知道那个昨天是不是被植入的记忆。总之今天出现了一系列怪异的事情,去买早点没遇到半个人,当然整个的人也没有,到了早餐店才发现没开门。稣想:算了,公司还有点东西可以撑一下肚子。

来到公司才发现更怪异的事,人倒是有,但都是骷髅。真佩服自己当时的淡定,居然没有大呼小叫神马的!不过当时马上想到世界末日,于是看看手机,时间是 2011 年,还早呢。好奇地打电话给家里,他们都表示世界没有抛出任何异常。然后又想:惨了,不会是只有软件园发生灾难吧!稣的弟弟也在软件园,马上打电话给他,他说公司送他们项目组的几个去北京玩了,不知道软件园发生了神马!

接着,发现脑子里还有一个人,可能又是被植入的女朋友,吧?就硬着头皮,往那处走过去,人不在位置,不过那屋子里的其他位置一样坐着几个骷髅,心想好像稣在意的人都没事,真是不幸中的万幸。

为了确保万无一失,稣给她打电话,没人接,不过不接不一定就是挂了,但是稣还不放心,到车库去看了一下,熟悉的车牌号,跑过去,震惊了……车里一个骷髅……车门打不开……过了一会儿,稣马上就冷静了,发现那个骷髅比她大了好几个尺寸,sigh~应该不是!

这时有人叫稣,一转身,居然是黄言!

黄言:都看见了?这视界总是要走向毁灭,躲得了初一,躲不过十五。

圣小开:稣一定要阻止战争!吓醒。

诗盗·大禹

《诗盗·大禹》:治水为命顺逆同,浩然莫测鬼神工。三过涂山生悟空,定海神针串鱼龙。

注解

改自霹雳角色“渊渟无迹静涛君”的诗号:

知水为命顺逆同,
浩然莫测深浅中。
无波沧海掩汹涌,
渊渟不动现鱼龙。

指针判空

引子

C 和 C++ 都有一些很基本的语句出现不同派系的写法,比如 * 靠左还是靠右,抑或居中?这种还是排版问题,并不影响有效字符数,但下面这个就直接影响有效字符数了!

《对于选择恐惧症患者来说,指针判空究竟要怎么写才不纠结?》

问题

指针判空有两大类写法:

  • if (p) {}

  • if (nullptr != p) {}

后者被挺多人推荐的,比如林锐的《高质量C++/C编程指南》。

高质量C++/C编程指南

搜一下,能发现不少网友都挺纠结。这么基础的问题,如果不交代清楚,就是给 C++ 黑很好的攻击理由。

故事

稣刚大学毕业时,是使用 C,并不屑 C++ 的,当时看了不少代码,就是直接 if (p)if (!p),所以也坚持这种写法,并认为这样写比较短,对手指好,比较养生。

后来 C++17 出现,稣就坚定地改用 C++,于是也更多遵守 C++ 类型安全的原则,开始认为 if (p)if (!p) 这种写法隐含类型转换,不太好。记得 2017 年以前,其实就看过 Google C++ Style Guide,里面也曾经建议写成 if (nullptr != p)if (nullptr == p)

现在,再去看 Google C++ Style Guide,已经没有这样的建议。

解决

C++ Core Guidelines 中有一条:ES.87: Don’t add redundant == or != to conditions,它的理由是:

Reason Doing so avoids verbosity and eliminates some opportunities for mistakes. Helps make style consistent and conventional.

if、while、for 的条件语句本身就是在选择 true 或 false,对于指针来说,会自动与 nullptr 比较。通常来说,if (p) 可以读作 “if p is valid”。

接着,还有一个例子:

1
2
3
if (auto pc = dynamic_cast<Circle>(ps)) { ... } // execute if ps points to a kind of Circle, good

if (auto pc = dynamic_cast<Circle>(ps); pc != nullptr) { ... } // not recommended

同样是推荐更简短的写法。但这个例子却有人提出疑问:推荐的写法不会被怀疑是 == 少写了一个 = 吗?

还真不会!这个语句其实不是经典 C++ 的语法,而是 C++17 的 init-statement 语法,这里的 auto 是类型,说明 = 不可能是 == 错误地少写成 =。

对于部分 C 程序员和“经典 C++”程序员会把赋值语句写在 if 条件里的做法,建议是改为分开写。

1
2
3
4
5
6
7
8
// p is defined before

if (p = func()) { ... } // bad

p = func();
if (p) { ... } // good

// p is used after

稣会在新工程里坚持使用 C++ Core Guidelines 的建议,但也不反对另一种写法,只要不混合使用。允许有不同派系,但最好别精神分裂。

BoringSSL? LibreSSL? OpenSSL!

问题

  • Windows 自带 OpenSSH 用的是 LibreSSL。要不要也用一下?甚至,直接动态链接到系统自带的 LibreSSL 的 dll,减少 exe 体积!

  • Chrome 用的是 BoringSSL,要不要……折腾一下?

尝试

稣使用 vcpkg,安装几个库,还不是手到擒来?

结果……连装都不让装!这是 LibreSSL 的:

1
2
3
4
5
Building libressl:x64-windows...
CMake Warning at ports/libressl/portfile.cmake:2 (message):
Can't build libressl if openssl is installed. Please remove openssl, and
try install libressl again if you need it. Build will continue since
libressl is a subset of openssl

这是 BoringSSL 的:

1
2
3
4
5
Building boringssl:x64-windows...
CMake Error at ports/boringssl/portfile.cmake:2 (message):
Can't build BoringSSL if OpenSSL is installed. Please remove OpenSSL, and
try to install BoringSSL again if you need it. Build will continue since
BoringSSL is a drop-in replacement for OpenSSL

在忍痛 vcpkg remove openssl 后,陷入一阵思考——稣主要通过 Boost.Asio 使用 SSL,所以问题转换为:Boost.Asio 对这几个 SSL 库支持得如何?其中,OpenSSL 是使用多年没有任何问题的,只需要调研其它两个!

先试试 LibreSSL,毕竟是 OpenBSD 的,而且微软也用它。拿 ClipboardSync 代码编译,发现顺利通过!但是运行时抛了异常,说不支持 TLS 1.3……遂查阅官网,说是从 3.2.0 就支持!那就是 Boost.Asio 不对了,果断给它提 issue

接着尝试 BoringSSL,毕竟是 Google 的,号称重视安全,而且有 Chrome 这个大型流行软件做背书!然而很打脸的是:它居然不支持 SM3!稣当年特地选择用国密标准里的 SM3 做 Hash 算法,就是因为爱国!不支持国密这点岂能忍?立刻 vcpkg remove boringssl

结论

Boost.Asio 和稣联合推荐 OpenSSL 为唯一好用又爱国的 SSL 库。

如果您想使用 Boost.Asio + LibReSSL,可以参考:https://github.com/UMU618/https_server

Boost【9】Boost.Beast

介绍

Boost.Beast 是一个 HTTP/WebSocket 库。本文只讨论 WebSocket。

相关经验

UMU 先是用过 WebSocket++(websocketpp),又用过 libwebsockets,再用的 Boost.Beast,之后就一直使用 Boost.Beast。

  • 2018 年,参与 EOS 开发时,它是用 WebSocket++ 的,跟着学习了一阵子。

  • 2020 年,在金山云时,内部版云游戏用 libwebsockets,跟着学习了一阵子。

  • 做开源版云游戏——鎏光云游戏引擎时,特地学习并使用 Boost.Beast。因为公司其实是要求和内部版有一些差异的,正好之前一直用 Boost.Asio,对它的熟悉可以快速套用在 Boost.Beast,于是果断切到 Boost.Beast。

什么场景应该使用 WebSocket?

如果您原本使用裸 TCP,您应该知道 TCP 的流式传输,导致您需要自己分包,即界定一个“消息”的边界。鎏光本来设计为同时支持裸 TCP 和 WebSocket 的,所以还保留着处理分包的代码:

https://github.com/UMU618/liuguang/blob/97f558275571b9be14893e4b55703b4b65cdbda5/src/cge/cge/game_session.cpp#L232

这对 WebSocket 其实并不需要,它的发送和接受已经是都是“消息”,带着长度的。所以如果您原来用 Asio 写 C/S 程序,把它们改为 Beast,是很容易的,而且代码量会缩减不少。

对于工具性的 C/S 程序,建议下次直接用 Beast 写更省事。

另一种适用场景是需要支持浏览器,即同时支持 C/S 和 B/S 模型。

Boost.Beast 经验

  1. text 和 binary 模式需要区分清楚,如果用于发送音视频,显然应该使用 binary 模式。

  2. stream 的默认接收长度是 16MiB,如果不够可以改长点,0 表示最大的 std::uint64_t。

参考:boost/beast/websocket/stream.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** Set the maximum incoming message size option.

Sets the largest permissible incoming message size. Message
frame fields indicating a size that would bring the total
message size over this limit will cause a protocol failure.

The default setting is 16 megabytes. A value of zero indicates
a limit of the maximum value of a `std::uint64_t`.

@par Example
Setting the maximum read message size.
@code
ws.read_message_max(65536);
@endcode

@param amount The limit on the size of incoming messages.
*/
void
read_message_max(std::size_t amount);

/// Returns the maximum incoming message size setting.
std::size_t
read_message_max() const;
  1. 默认开了 deflate 压缩。UMU 在开发 ClipboardSync 时,曾经考虑给剪切板数据压缩,对比了 lz4 和 zstd,很是犹豫,后来发现 Beast 默认开了 deflate 压缩,于是放弃自己用 lz4 或 zstd 压缩。

自动添加 IP 黑名单到防火墙

问题

前文《防爆破远程桌面密码》提到可以用 Powershell 实现自动加 IP 黑名单到防火墙,这个坑还是得填,毕竟爆破依然在持续……

分析

核心点:

  • IP 黑名单会持续新增,过去已经加入防火墙的名单也需要保存。所以应该把新增 IP 和防火墙已有 IP 求并集。

  • 如果名单没变,不应该覆盖防火墙规则。

代码

见 Github 仓库:

https://github.com/UMU618/scripts/blob/master/pwsh/add-ip-blacklist-to-firewall.ps1