ASCII 控制字符有哪些?各自在编程中怎么用?
ASCII 控制字符是 ASCII 编码表中编号 0-31 和 127 的 33 个不可见字符,它们不表示可打印的符号,而是用于控制设备行为、格式化文本和管理数据传输。在现代编程中,虽然大部分控制字符已经很少直接使用,但 NUL、LF、CR、HT、ESC、DEL 等仍然无处不在。
核心答案:33 个控制字符一览
ASCII 控制字符分为四大类:
| 类别 | 字符 | 十六进制 | 用途 |
|---|---|---|---|
| 通信控制 | SOH/STX/ETX/EOT/ENQ/ACK/NAK/SYN/ETB/DLE | 01-06,15-17,22 | 数据传输协议 |
| 格式控制 | BS/HT/LF/VT/FF/CR | 08-0D | 文本排版 |
| 信息分隔 | FS/GS/RS/US | 1C-1F | 数据逻辑分隔 |
| 其他 | NUL/BEL/CAN/SUB/ESC/SI/SO/DC1-DC4/DEL | 00,07,18-1F,7F | 特殊功能 |
面试中最常考的几个:NUL(0) 是 C 语言字符串终止符,LF(10) 是 Unix 换行,CR(13) 是回车,ESC(27) 开启转义序列,DEL(127) 是删除。
通信控制字符:数据传输的信号灯
通信控制字符诞生于 1960 年代的串口通信时代,用于在两个设备之间建立可靠的数据交换协议。
- SOH (0x01) — 标题开始,标记消息头的起始位置,在早期串口通信中用于区分报文头部和正文
- STX (0x02) / ETX (0x03) — 正文开始/结束,两者成对使用框定有效文本内容
- EOT (0x04) — 传输结束,在 Unix 终端中 Ctrl+D 会发送 EOT,表示输入流结束(EOF 的底层实现之一)
- ENQ (0x05) / ACK (0x06) / NAK (0x15) — 询问/确认/否认,三者构成最基础的握手协议:发送方发 ENQ 询问,接收方回 ACK 确认或 NAK 拒绝
- SYN (0x16) — 同步空闲,在异步通信中用于维持收发双方的时钟同步
- DLE (0x10) — 数据链路转义,解决数据流中恰好出现与控制字符相同字节的问题,DLE 之后的内容按数据而非控制指令解读
- ETB (0x17) — 传输块结束,将长数据分割为多个块传输时标记每个块的边界
格式控制字符:文本排版的底层机制
格式控制字符直接影响文本的布局和呈现,是日常编程中接触最多的控制字符。
- BS (0x08) — 退格,将光标向左移动一格。在终端中常用于实现"叠打"效果,比如先输出字符再退格输出下划线来实现粗体
- HT (0x09) — 水平制表符,跳到下一个制表位(默认间距为 8 的倍数)。Makefile 的缩进规则强制要求使用 Tab 而非空格,这是 HT 在现代工具链中最独特的存在
- LF (0x0A) — 换行,将光标垂直下移一行。C 语言和 Unix 系统用它单独表示新行
- VT (0x0B) — 垂直制表符,将光标下移到下一个垂直制表位,现代几乎不再使用
- FF (0x0C) — 换页,指示打印机跳到下一页开头。部分终端模拟器用它清屏
- CR (0x0D) — 回车,将光标移到当前行首。与 LF 配合使用的历史非常悠久
CR 与 LF:一个跨平台的经典陷阱
不同操作系统对换行的实现不同,这是 ASCII 控制字符在实际开发中最常见的坑:
- Windows:CRLF (
\r\n,0x0D+0x0A),两个字节的组合完成"回车+换行" - Unix/Linux:LF (
\n,0x0A),一个字节搞定 - 旧版 Mac OS (9 及之前):CR (
\r,0x0D),只用回车
这导致 Windows 上编辑的文件在 Linux 中每行末尾多出 ^M,Git 的 core.autocrlf 配置就是为了处理这个问题。在串口通信和协议开发中,必须严格区分 CR 和 LF:比如 AT 指令必须以 CR(\r)结尾而非 LF。
NUL:C 语言字符串的基石
NUL (0x00) 是 ASCII 表的第一个字符,也是 C 语言字符串最关键的控制字符。C 语言字符串以 \0 结尾,这个约定贯穿了整个 C 标准库:
cchar str[] = "Hello\0World"; printf("%s", str); // 输出: Hello // strlen 遇到第一个 \0 就停止计数 printf("%zu", strlen(str)); // 输出: 5
NUL 作为字符串终止符的设计是 C 语言诸多安全问题的根源——如果忘记添加 \0,strlen、strcpy 等函数会越界读取内存,这是缓冲区溢出漏洞的常见成因。
NUL 字节注入(Null Byte Injection)也是 Web 安全中的一个经典攻击手法:在文件路径中插入 \0 可以截断字符串,绕过文件扩展名检查,比如 ../../../etc/passwd\0.jpg 在某些旧版 C 库实现中会被解读为 ../../../etc/passwd。
ESC 和 DEL:扩展与删除
ESC (0x1B) 是 ASCII 标准中最具扩展性的设计。它本身不执行任何操作,而是作为转义序列的开头,与其后的字符组合产生新的控制功能。终端中的 ANSI 转义码就基于此:\x1b[31m 将文字变为红色,\x1b[2J 清屏,\x1b[H 将光标移到左上角。Vim 的 ESC 键返回 Normal 模式,也是这个字符的历史延续。
DEL (0x7F) 的编号不在 0-31 而是排在 127,原因是纸带编码用 7 个孔位表示数据,0x7F 对应所有孔位全部打穿——在纸带上物理地抹除一个字符。现代键盘中 Delete 键的功能已经改变,但在终端中 Ctrl+?(或 Ctrl+Backspace)发送的仍是 0x7F。
设备控制与信息分隔
DC1-DC4 (0x11-0x14) 是设备控制字符,其中 DC1 和 DC3 至今仍在串口流控中使用,分别称为 XON 和 XOFF。当接收缓冲区快满时发送 XOFF 暂停传输,处理完再发 XON 恢复——这是软件流控制的标准机制。
FS/GS/RS/US (0x1C-0x1F) 是信息分隔字符,按层级从高到低分隔数据单元:文件 > 组 > 记录 > 单元。它们在串行存储时代用于在连续数据流中划分逻辑边界,类似现代 CSV 文件中的逗号和换行。虽然现代协议已用 JSON/XML 替代,但部分老旧金融系统和工业协议仍在使用。
为什么 127 是控制字符但 32 不是
空格 (0x20) 在 ASCII 中是一个特殊的存在——它不可见,但被归类为可打印字符而非控制字符。原因在于空格确实在文本中占据一个可见的排版位置(光标右移一格),而控制字符只控制设备行为不占据排版位置。DEL (0x7F) 虽然编号超出了 0-31 的范围,但它的功能是删除/抹除,属于控制行为,因此归入控制字符。
面试常见追问
问:为什么 Windows 用 CRLF 而 Unix 只用 LF? 早期的电传打字机需要两个动作完成换行:CR 把打印头移回行首,LF 把纸向上卷一行。Unix 的设计者认为在电子时代用一个 LF 同时完成两个动作更合理,而 Windows 沿用了硬件时代的传统。
问:NUL 和空格有什么区别?
NUL (0x00) 的所有二进制位都是 0,在 C 语言中标志字符串结束;空格 (0x20) 的二进制是 00100000,是一个占位的可打印字符。\0 不可见也不占排版, 不可见但占排版。
问:如何在代码中检测字符串是否包含控制字符?
遍历每个字符,检查其 ASCII 值是否在 0-31 或等于 127。Python 中可用 ord(c) < 32 or ord(c) == 127 判断。正则表达式 [\x00-\x1F\x7F] 也能匹配。