为什么使用三字符组时GCC会发出警告,但使用二字符组时却不发出警告?

7

代码:

#include <stdio.h>

int main(void)
{
  ??< puts("Hello Folks!"); ??>
}

当使用GCC 4.8.1编译上述程序并加上-Wall-std=c11选项时,会出现以下警告:

source_file.c: In function ‘main’:
source_file.c:8:5: warning: trigraph ??< converted to { [-Wtrigraphs]
     ??< puts("Hello Folks!"); ??>
 ^
source_file.c:8:30: warning: trigraph ??> converted to } [-Wtrigraphs]

但是当我将 main 的主体更改为以下内容时:
<% puts("Hello Folks!"); %>

没有任何警告被抛出。

那么,为什么使用三连符时编译器会发出警告,而使用双连符时却不会?


1
可能是为什么三字符组在现代C++编译器中会生成错误的重复问题。 - Wintermute
@ShafikYaghmour 我认为那里的答案包含了所有可能在回答这个问题时提供的信息,即使新版本(或不同的前端?)的gcc将其对三字符组的处理降级为警告。 - Wintermute
@ShafikYaghmour 这个链接的问题仍然说gcc生成警告,而错误来自Turbo C。所以我认为自那以后没有任何改变。 - P.P
@BlueMoon 我突然意识到行为显示不同的原因是现在几乎每个人都在使用 -std=xxx,这意味着 gcc 会自动打开三字符。因此也许我同意这是重复的。 - Shafik Yaghmour
三字符和双字符符号的使用源于很多/大多数键盘没有相应按键的时代。如今,这已经过时,不应该再使用。 - user3629249
4个回答

6
由于三字符序列会 悄悄地 更改代码,因此它们具有不良影响。这意味着相同的源文件无论是否进行三字符序列替换都是有效的,但会导致不同的代码。这在字符串字面值中尤为棘手,比如 "<em>What??</em>"
语言设计和语言演进应该努力避免悄悄地更改代码。编译器警告三字符序列是一个好的做法。
与三字符序列相比,双字符序列是 新的标记,不会导致静默更改。

5
这篇gcc预处理文档给出了关于警告的充分理由 (强调是我的):

三连符(trigraphs)并不受欢迎,许多编译器对它们的实现也存在问题。可移植代码不应该依赖于三连符被转换或忽略。使用-Wtrigraphs编译选项,GCC将在以下情况下发出警告:如果三连符被转换后可能会改变程序含义,则GCC会提醒您。

而在这篇关于标记化的GCC文档中,解释道与三连符不同,双排符没有潜在的负面影响 (强调是我的):

还有六个双排符(digraphs),C++标准将其称为替代标记(alternative tokens),只是其他标点符号的另一种拼写方式。这是第二次尝试解决陈旧系统中缺失标点符号的问题。它没有像三连符那样产生负面影响


1
这并没有回答为什么双字符不会抛出警告(或者这意味着它们更受欢迎)。 - schnaader
@schnaader,这是暗示,但我添加了另一个文档来明确说明。 - Shafik Yaghmour
所有三个答案都讲了同样的事情,但我喜欢你的答案,因为它包括简短、正确的引用。勾选给你! :) - Spikatrix

4
也许是因为它没有负面影响,与三字母组不同,如 GCC 文档中所述:

标点符号是C和C ++有意义的所有常规标点符号。 ASCII中除3个标点符号“@”,“$”和“`”以外的所有标点符号都是C标点符号。此外,所有两个和三个字符运算符都是标点符号。还有六个双字母组,C ++标准称为替代令牌,这仅是拼写其他标点符号的替代方式。这是第二次尝试解决过时系统中缺少标点符号的问题。它没有三字母组负面影响,但覆盖范围不够广泛。双字母组及其对应的正常标点符号如下:

 Digraph:        <%  %>  <:  :>  %:  %:%:
 Punctuator:      {   }   [   ]   #    ##

3

三字符序列很讨厌,因为它们使用的字符序列在有效代码中也可能合法出现。在经典 Macintosh 上编写的代码中,这种情况通常会导致编译器错误:

unsigned int signature = '????';  /* Should be value 0x3F3F3F3F */

三字符处理将把它转换为:
unsigned int signature = '??^;  /* Should be value 0x3F3F3F3F */

这段代码显然无法编译。在一些较为罕见的情况下,这种处理方式可能会生成可以编译的代码,但与预期含义有所不同,例如:

char *template = "????/1234";

这将会被转换为

char *template = "??S4"; // ??/ becomes \, and \123 becomes S

虽然不是本意的字符串字面值,但仍然是完全合法的。

相比之下,双字符在宏处理的一些奇怪角落情况下可能会有问题,但是没有进行处理的代码中包含可处理的双字符也是合法的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接