C语言trigraph是什么?

99ANYc3cd6
预计阅读时长 13 分钟
位置: 首页 C语言 正文

什么是 Trigraph?

Trigraph(三字符序列)是 C 语言标准中定义的一组三个字符的组合,用于表示另一个特定的字符,它的设计初衷是为了解决某些古老的键盘(比如某些欧洲键盘)缺少某些标准 ASCII 字符(如 , , [, ], , \, ^, , )的问题。

C语言trigraph
(图片来源网络,侵删)

程序员可以通过输入三个特定的字符序列,让编译器将其替换成它所代表的那个目标字符。

为什么会有 Trigraph?

这要追溯到 C 语言标准化的早期(C89/C90),当时,许多终端和键盘布局不完整,无法直接输入所有 C 语言需要的标点符号,为了确保代码的可移植性,标准委员会引入了 Trigraph 机制,作为一种“逃生舱口”,让程序员可以在任何键盘上编写符合 C 语法的代码。

所有的 Trigraph 序列

C 语言标准一共定义了 9 个 Trigraph 序列,它们都以两个问号 开头,后面跟一个字符:

Trigraph 序列 代表的字符 描述
Hash (井号)
[ 左方括号
\ 反斜杠
] 右方括号
^ Caret (脱字符)
??< 左花括号
竖线
??> 右花括号
波浪号

Trigraph 如何工作?

Trigraph 的替换是由编译器预处理阶段完成的,在编译器开始解析代码的语法之前,一个叫做“Trigraph 替换”的步骤会扫描整个源文件,将所有识别到的 Trigraph 序列替换成它们对应的字符。

C语言trigraph
(图片来源网络,侵删)

示例:

假设你有以下代码:

// my_code.c
??<
  printf("Hello, world??!\n");
??

在预处理阶段,编译器会首先进行替换:

  1. ??< 会被替换成
  2. 会被替换成

替换后的代码(编译器实际看到的)变成了:

// 这是编译器在预处理后看到的代码
{
  printf("Hello, world|\n");
}

编译器才会对这个替换后的代码进行语法分析和编译。

Trigraph 的实际应用和注意事项

1 在现代编程中的地位:已废弃

由于现代计算机和键盘已经完全支持所有标准 ASCII 字符,Trigraph 的原始用途已经消失,在 C11 标准中,Trigraph 被标记为“可选功能”(obsolescent feature),并且默认情况下是关闭的

  • GCC/Clang: 默认情况下不启用 Trigraph,如果你想启用它,需要使用命令行参数 -trigraphs
  • MSVC (Visual Studio): 默认情况下也不支持 Trigraph。

2 主要用途:阅读和维护旧代码

你今天最有可能遇到 Trigraph 的场景是阅读 90 年代或更早时期编写的代码,维护这些代码时,你必须了解 Trigraph 的存在,否则代码的语法会看起来非常奇怪。

3 一个常见的“陷阱”

考虑下面这个例子:

// This is a comment ??/
  • 一个没有经验的程序员可能会认为 是注释的一部分。
  • 但实际上,在预处理阶段, 会被替换成 \
  • 这行代码实际上变成了:
    // This is a comment \
  • 根据C语言规则,反斜杠 \行继续符,它会将下一行连接到当前行。
  • 如果下一行恰好是代码,那么注释就会意外地“溢出”到代码行,导致编译错误。

示例:

// file.c
#include <stdio.h>
int main() {
    // This is a comment ??/
    printf("Hello"); // This line is now part of the comment!
    return 0;
}

在预处理后,它变成了:

#include <stdio.h>
int main() {
    // This is a comment \
    printf("Hello"); // This line is now part of the comment!
    return 0;
}

编译器会认为 printf 仍然是注释的一部分,从而导致 main 函数中没有任何有效代码,编译失败。

如何在 GCC/Clang 中演示 Trigraph?

如果你想亲眼看一下 Trigraph 是如何工作的,可以手动启用它。

创建一个包含 Trigraph 的源文件 test_trigraph.c

// test_trigraph.c
// ??=include <stdio.h>
int main(void) {
    printf("Using trigraphs: ??<Hello, world??>??!\n");
    return 0;
}

不启用 Trigraph 进行编译(默认行为):

gcc -E test_trigraph.c

输出会是未经替换的原始代码:

# 1 "test_trigraph.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test_trigraph.c"
// ??=include <stdio.h>
int main(void) {
    printf("Using trigraphs: ??<Hello, world??>??!\n");
    return 0;
}

可以看到, 和 ??<, 都原封不动。

启用 Trigraph 进行编译:

gcc -E -trigraphs test_trigraph.c

输出是替换后的代码:

# 1 "test_trigraph.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test_trigraph.c"
#include <stdio.h>
int main(void) {
    printf("Using trigraphs: {Hello, world}|\n");
    return 0;
}

可以看到, 变成了 ,??< 变成了 , 变成了 。

特性 描述
定义 三个字符的组合,用于代表另一个字符。
目的 解决老旧键盘缺少特定符号的问题,增强代码的可移植性。
阶段 预处理阶段由编译器自动完成替换。
现状 已废弃,C11 标准中默认关闭,现代编译器(GCC, Clang, MSVC)默认不支持。
主要用途 阅读和理解非常古老的 C 代码
建议 永远不要在新代码中使用 Trigraph,它只会带来困惑和潜在的错误,如果你必须维护旧代码,请确保编译器正确处理(或手动识别)它们。

Trigraph 是 C 语言发展史上的一个有趣遗迹,了解它有助于你更好地理解语言的演变和兼容性问题,但在日常实践中,你可以完全忽略它。

-- 展开阅读全文 --
头像
dede提交表单内容
« 上一篇 04-16
dede价格区间调用
下一篇 » 04-16

相关文章

取消
微信二维码
支付宝二维码

目录[+]