编程语言研究书库

字符编码与重定向 — 从 ASCII 到 UTF-8,以及 `>` 的魔法

字符编码与重定向 — 从 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 不动。

  1. 编码 = 映射(码点) + 方案(字节序/变长);分开谈才不会混;
  2. UTF-8 赢在ASCII 兼容 + 无字节序,这是「事实标准」的判据示范;
  3. 矢量字体 = 轮廓 + 栅格化;FreeType 管全流程;
  4. 库有隐藏成本(栈/堆),链进来之前想想它的资源胃口;
  5. 重定向 = fd 替换;stdout 可换、stderr 保留、用完还原;
  6. 「一切皆文件」每兑现一次(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(搜「被继承」)

Footnotes

  1. 出处:「28.1 日文和字符编码」(text/175-ch28-01-28-1-osbook-day28a.txt:30,搜「两件事」;:32,搜「码位」;:36,搜「编码方案」)。 ↩

  2. 出处:「28.1 日文和字符编码」(text/175-ch28-01-28-1-osbook-day28a.txt:9,搜「Shift-JIS」);:22,搜「头疼」)。 ↩

  3. 出处:「28.1 日文和字符编码」(text/175-ch28-01-28-1-osbook-day28a.txt:37,搜「UTF-32」;:43,搜「代理对」;:46,搜「可变长度」;:54,搜「字节序」)。 ↩

  4. 出处:「28.1 日文和字符编码」(text/175-ch28-01-28-1-osbook-day28a.txt:52,搜「完全相同」;:60,搜「事实上的全球标准」;:62,搜「UTF-8实现日文」)。 ↩

  5. 出处:「28.1 日文和字符编码」(text/175-ch28-01-28-1-osbook-day28a.txt:70,搜「WriteUnicode」;:78,搜「UTF-8编码字符串」);:72,搜「0x7f以下」)。 ↩

  6. 出处:「28.2 日文字体」(text/176-ch28-02-28-2-osbook-day28b.txt:12,搜「FreeType」;:13,搜「TrueType」)。 ↩

  7. 出处:「28.2 日文字体」(text/176-ch28-02-28-2-osbook-day28b.txt:24,搜「流程」;:46,搜「FT_New_Memory_Face」;:67,搜「FT_Load_Glyph」;:70,搜「bitmap」)。 ↩

  8. 出处:「28.2 日文字体」(text/176-ch28-02-28-2-osbook-day28b.txt:82,搜「溢出了栈区域」)。 ↩

  9. 出处:「28.3 重定向」(text/177-ch28-03-28-3-osbook-day28c.txt:19,搜「重定向(redirect)」)。 ↩

  10. 出处:「28.3 重定向」(text/177-ch28-03-28-3-osbook-day28c.txt:40,搜「自动输出到该文件」));:55,搜「重定向符号」)。 ↩

  11. 出处:「28.3 重定向」(text/177-ch28-03-28-3-osbook-day28c.txt:63,搜「CreateFile」;:99,搜「被继承」)。 ↩

  12. 出处:「28.3 重定向」(text/177-ch28-03-28-3-osbook-day28c.txt:67,搜「files_[2]」;:92,搜「错误信息」;:99,搜「还原」)。 ↩

On this page