字符编码与重定向 — 从 ASCII 到 UTF-8,以及 > 的魔法¶
这一章讲什么: 两件看似不搭界的事。 第一件:让日文上屏。 字符编码有过一段乱世(日文就有 JIS、Shift-JIS、EUC-JP 三套), Unicode 用一个码点系统统一了字符,UTF-8 用「ASCII 兼容、没有字节序问题」赢下编码方案之争; 但会解码不等于能显示——还得请 FreeType 把 TrueType 矢量字体栅格成点阵。 第二件:重定向。
echo hoge > file的秘密简单得可爱: 把 stdout 的 fd 换成文件的 fd——第 26 章「一切皆文件」的又一次分红。
1. 先看现象:一行日文、一个大于号¶
主线例子:
cat jpn.txt
→ 终端按 UTF-8 逐字节解码出码点 → WriteUnicode(码点)
→ FreeType 从 nihongo.ttf 找字形 → 栅格化成位图 → 日文上屏(不再是 ??)
echo hoge > piyo
→ 终端解析命令行发现 '>' → open(piyo)(没有就创建空文件)
→ files_[1](stdout)换成文件 fd → printf 照常写,落进了文件
→ 命令结束还原 files_[1];files_[2](stderr)保留给错误信息
2. 顶层全景¶
28.1 编码:ASCII 7 位;日文三码并立;Unicode = 码点映射 + 编码方案(UTF-8/16/32);
UTF-8 的两张王牌;WriteString 内置 UTF-8 解码器
28.2 字体:TrueType 矢量字体 + FreeType(初始化 → 载入内存字体 → 码点→字形编号
→ 载字形 → 栅格位图 → 画);任务栈 4KiB 被 FreeType 撑爆 → 加大
28.3 重定向:> 的解析;files_[1] 换 fd;文件存在即截断式使用;
files_[2] 留给 stderr;用完还原(否则污染下一条命令)
图说:编码与字体是「理解字形」的两半;重定向是「fd 可替换」的一行兑现。
3. 核心原理¶
3.1 编码:一张对照表 + 一把写法的钥匙¶
字符编码 = 两件事:字符↔数值的映射(Unicode 里这个数值叫码点, 如 ♪ = U+266A),以及数值↔字节串的转换方法(编码方案)1。 ASCII 只到 127;日文曾经 JIS、Shift-JIS、EUC-JP 三分天下,连「切换英文/日文模式」 这种咒语都存在(JIS 的切换标记)——「光是想想我们就头疼」2。 Unicode 用一个系统装下全世界字符;UTF-32(一字符 32 位,简单但 4 倍膨胀)、 UTF-16(16 位 + 代理对,作者直评「难用」)、UTF-8(1–4 字节变长)三选一。 UTF-8 的两张王牌:ASCII 字符编码后与 ASCII 完全相同(旧程序能读、英文不涨体积), 以及没有字节序问题(多字节字符的字节顺序由规范定死)3。 「UTF-8 已成为事实上的全球标准」——MikanOS 跟随4。 Terminal::Print/cat 因此重写:WriteString 内嵌一个 UTF-8 解码器(把字节翻译回字符的程序), 逐字符解出码点,交给 WriteUnicode 画——不过当时还没有 0x7F 以上的字体,先解不先画5。
3.2 字体:FreeType 把矢量轮廓变成点阵¶
8×16 点阵只装得下 ASCII;日文上万字符,必须上 TrueType 矢量字体 + FreeType 库6。 流程五步:FT_Init_FreeType 初始化 → FT_New_Memory_Face 从内存里的 nihongo.ttf 打开字体 → 经字符映射表把码点翻成字形编号 → FT_Load_Glyph 载字形 → face->glyph->bitmap 里的位图画上屏7。 一个真实的坑:FreeType 吃栈太凶,把任务栈 4 KiB 撑爆、系统重启—— 栈大小因此加大8。——「库链接进来就能用」的边界,被栈尺寸上了一课。
3.3 重定向:换一个 fd¶
重定向 = 把标准输出接到终端以外的设备9。实现的关键句:
「将 files_[1] 复制为文件描述符,应用将自动输出到该文件」10。
流程:解析命令行发现 > → 取目标路径,open(不存在则 CreateFile) →
files_[1] = 文件 fd → 应用该干嘛干嘛 → 命令结束还原 files_[1]
(不还原,重定向会被下一条命令继承)11。
files_2保持指向终端——错误信息不该混进重定向文件;
将来 stderr 也能重定向,错误落文件、正常输出上屏12。
4. 作者的判断与证据¶
判断:跟随事实标准(UTF-8),不自造轮子。 编码之争的评判标准写得具体可核: ASCII 兼容、无字节序、体积不膨胀——三条都指向 UTF-8。
判断:库能链就用,但替库付账。 FreeType 链进来一行没改库代码, 代价是栈溢出——库的隐藏成本(栈、堆、ABI)要付,付完才是「能用」。
证据: cat jpn.txt 出日文(图 28.3);echo/rpn 重定向进文件再 cat 验尸(图 28.4)。
5. 边界与局限¶
- 只读不写日文:输入侧(键盘→日文)未做;UTF-8 解码器只管显示;
- 字体只有一张 nihongo.ttf,缺字就是方框;
- 重定向只有
>(输出覆盖),追加重定向>>、输入重定向<是习题; - FreeType 链进内核(不是应用侧)——内核体积与栈成本的权衡选了省事。
6. 可带走的¶
本章主线例子,一行写完: UTF-8 解码 → WriteUnicode → FreeType 栅格 TrueType → 日文上屏;> 解析 → files_[1] 换文件 fd → 用完还原,stderr 不动。
- 编码 = 映射(码点) + 方案(字节序/变长);分开谈才不会混;
- UTF-8 赢在ASCII 兼容 + 无字节序,这是「事实标准」的判据示范;
- 矢量字体 = 轮廓 + 栅格化;FreeType 管全流程;
- 库有隐藏成本(栈/堆),链进来之前想想它的资源胃口;
- 重定向 = fd 替换;stdout 可换、stderr 保留、用完还原;
- 「一切皆文件」每兑现一次(fd 读键盘、fd 写文件),shell 就少一条特殊规则。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 编码的两件事与码点 | 28.1 日文和字符编码(osbook_day28a) | text/175-ch28-01-28-1-osbook-day28a.txt:30(搜「两件事」) · text/175-ch28-01-28-1-osbook-day28a.txt:32(搜「码位」) |
| 日文三码与头疼 | 28.1 日文和字符编码(osbook_day28a) | text/175-ch28-01-28-1-osbook-day28a.txt:9(搜「Shift-JIS」) · text/175-ch28-01-28-1-osbook-day28a.txt:22(搜「头疼」) |
| UTF-32/16/8 对比 | 28.1 日文和字符编码(osbook_day28a) | text/175-ch28-01-28-1-osbook-day28a.txt:37(搜「UTF-32」) · text/175-ch28-01-28-1-osbook-day28a.txt:43(搜「代理对」) · text/175-ch28-01-28-1-osbook-day28a.txt:46(搜「可变长度」) |
| UTF-8 两张王牌与定论 | 28.1 日文和字符编码(osbook_day28a) | text/175-ch28-01-28-1-osbook-day28a.txt:52(搜「完全相同」) · text/175-ch28-01-28-1-osbook-day28a.txt:54(搜「字节序」) · text/175-ch28-01-28-1-osbook-day28a.txt:60(搜「事实上的全球标准」) |
| WriteUnicode 与解码 | 28.1 日文和字符编码(osbook_day28a) | text/175-ch28-01-28-1-osbook-day28a.txt:70(搜「WriteUnicode」) · text/175-ch28-01-28-1-osbook-day28a.txt:78(搜「UTF-8编码字符串」) |
| FreeType 流程 | 28.2 日文字体(osbook_day28b) | text/176-ch28-02-28-2-osbook-day28b.txt:12(搜「FreeType」) · text/176-ch28-02-28-2-osbook-day28b.txt:24(搜「流程」) · text/176-ch28-02-28-2-osbook-day28b.txt:67(搜「FT_Load_Glyph」) |
| 栈溢出 | 28.2 日文字体(osbook_day28b) | text/176-ch28-02-28-2-osbook-day28b.txt:82(搜「溢出了栈区域」) |
| 重定向定义与关键 | 28.3 重定向(osbook_day28c) | text/177-ch28-03-28-3-osbook-day28c.txt:19(搜「重定向」) · text/177-ch28-03-28-3-osbook-day28c.txt:40(搜「自动输出到该文件」) |
| > 解析、stderr 与还原 | 28.3 重定向(osbook_day28c) | text/177-ch28-03-28-3-osbook-day28c.txt:55(搜「重定向符号」) · text/177-ch28-03-28-3-osbook-day28c.txt:67(搜「files_[2]」) · text/177-ch28-03-28-3-osbook-day28c.txt:99(搜「被继承」) |
-
出处:「28.1 日文和字符编码」(
text/175-ch28-01-28-1-osbook-day28a.txt:30,搜「两件事」;:32,搜「码位」;:36,搜「编码方案」)。 ↩ -
出处:「28.1 日文和字符编码」(
text/175-ch28-01-28-1-osbook-day28a.txt:9,搜「Shift-JIS」);:22,搜「头疼」)。 ↩ -
出处:「28.1 日文和字符编码」(
text/175-ch28-01-28-1-osbook-day28a.txt:37,搜「UTF-32」;:43,搜「代理对」;:46,搜「可变长度」;:54,搜「字节序」)。 ↩ -
出处:「28.1 日文和字符编码」(
text/175-ch28-01-28-1-osbook-day28a.txt:52,搜「完全相同」;:60,搜「事实上的全球标准」;:62,搜「UTF-8实现日文」)。 ↩ -
出处:「28.1 日文和字符编码」(
text/175-ch28-01-28-1-osbook-day28a.txt:70,搜「WriteUnicode」;:78,搜「UTF-8编码字符串」);:72,搜「0x7f以下」)。 ↩ -
出处:「28.2 日文字体」(
text/176-ch28-02-28-2-osbook-day28b.txt:12,搜「FreeType」;:13,搜「TrueType」)。 ↩ -
出处:「28.2 日文字体」(
text/176-ch28-02-28-2-osbook-day28b.txt:24,搜「流程」;:46,搜「FT_New_Memory_Face」;:67,搜「FT_Load_Glyph」;:70,搜「bitmap」)。 ↩ -
出处:「28.2 日文字体」(
text/176-ch28-02-28-2-osbook-day28b.txt:82,搜「溢出了栈区域」)。 ↩ -
出处:「28.3 重定向」(
text/177-ch28-03-28-3-osbook-day28c.txt:19,搜「重定向(redirect)」)。 ↩ -
出处:「28.3 重定向」(
text/177-ch28-03-28-3-osbook-day28c.txt:40,搜「自动输出到该文件」));:55,搜「重定向符号」)。 ↩ -
出处:「28.3 重定向」(
text/177-ch28-03-28-3-osbook-day28c.txt:63,搜「CreateFile」;:99,搜「被继承」)。 ↩ -
出处:「28.3 重定向」(
text/177-ch28-03-28-3-osbook-day28c.txt:67,搜「files_[2]」;:92,搜「错误信息」;:99,搜「还原」)。 ↩