有向字母和三字母不能一起工作?

Digraph and trigraph can't work together?

我正在学习二字母和三字母,这是我看不懂的代码。 (是的,我承认非常丑。)

这段代码可以编译:

#define _(s) s%:%:s

main(_(_))
<%
    __;
%>t

这段代码也可以编译:

#define _(s) s??=??=s

main(_(_))
<%
    __;
%>

但是下面两段代码都无法通过:

#define _(s) s%:??=s

main(_(_))
<%
    __;
%>

#define _(s) s??=%:s

main(_(_))
<%
    __;
%>

这确实让我感到困惑:由于前两段代码可以编译,我想二字母和三字母的展开都发生在宏展开之前。那么为什么二字母三字母一起使用会编译不通过呢?

二字母和三字母完全不同。三字母在翻译的第 1 阶段被替换,[见注释 1] 在源代码被分离成标记之前。 Digraphs 是标记,它是其他标记的替代拼写,因此在源被分离成标记之前它们没有意义。 (单词 "digraph" 不是很准确;使用它是因为它类似于 "trigraph",但二合字母集包括由四个字符组成的 %:%:。)

因此在完成任何标记分析之前,??= 被替换为 #。但是%:只是一个token,和#.

的意思是一样的

此外,%:%:是一个与##同义的记号。但是 %:# 是两个标记(%:#),这是不合法的,因为 stringify 运算符(无论拼写为 %: 还是 #)只能跟在后面通过宏参数。 [见注释 2] 如果 # 是三字母替换的结果,它也不会变得不合法。

所示,二字母和三字母之间的一个重要区别是三字母也适用于字符串。即使您的键盘缺少方括号和八字形,二合字母仍可让您编写 C 代码,但它们无法帮助您打印出这些字符。


注释(标准引用):

  1. §5.1.1.2,翻译阶段,第 1 段:

    The precedence among the syntax rules of translation is specified by the following phases.

    1. Physical source file multibyte characters are mapped, in an implementation-defined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations.
  2. §6.10.3.2,# 运算符,第 1 段:

    Each # preprocessing token in the replacement list for a function-like macro shall be followed by a parameter as the next preprocessing token in the replacement list.

对于学术方面,请查看 rici 的有据可查的答案。

对于常识而言,除非你已经非常精通C,否则二字母和三字母是完全没有用的,你甚至不应该在这个问题上浪费任何时间。它们的发明是为了支持 non-US 7 位字符集,这些字符集在 1980 年代仍在大型机和一些小型计算机上使用。这些字符集缺少 C 语言所需的一些标点符号,例如 #{} 等,以便为 space 等区域设置特定字符制作 space 14=]、éè...(请原谅我的法语)。

即使在我使用了很长时间的这些系统上,也从未使用过三字母,因为存在丑陋的实用替代品:在法语系统上,输入了重音字母,例如 éè但会被 C 编译器解释为 {}。它使 C 编程变得晦涩难懂,并促使许多程序员改用美式 QWERTY 键盘和语言环境(或等效语言)。

这已经成为过去,只有历史意义,除了拼写错误、混淆和令人讨厌的面试问题外,你永远不会看到这些东西在起作用。

关于后者,我忍不住发了这个:

I cannot get fnmatch to validate my date template even if I force a valid date, what is wrong with this code:

#include <stdio.h>
#include <fnmatch.h>
int main() {
    char date[] = "01/01/1988";
    if (fnmatch("??/??/????", date, 0))
        printf("invalid date format\n");
    return 0;
}