c/c++ digraph and trigraph

来源:互联网 发布:学网络营销好还是java 编辑:程序博客网 时间:2024/06/03 19:16


转载:http://blog.csdn.net/dqjyong/article/details/8048829

在编写程序时需要时时提防编译器背着我们干一些没有通知我们做的事,下面列举转义字符对程序的影响。

          首先列出二字符组和三字符组对应的意思。
          二元字符    等价字符
     <:        [
     :>        ]
     <%        {
     %>        }
    %:或者%%    #
    三元字符  等价字符
     ??=       #
     ??/       \
     ??'       ^
     ??(       [
     ??)       ]
     ??!       |
     ??<       {
     ??>       }
     ??-       ~
     值得庆幸的是,二元字符或者三元字符转义之后不能再与二元字符或者三元字符结合,要不然程序员可能会疯了。闲话少说,那么为什么会c++标准允许出现这种情况呢?
      根据 Intention to deprecate trigraphs in the next C++ Standard可以知道这有两个原因:
      1.由于类似“#\”等这些字符在EBCDIC的代码页中用来区分代码点的。在所有的EBCDIC的代码页中使用“?”和"="不会分割代码页,而会共享同一个代码点。
      2.其次是因为一些国际化的键盘设置原因,有些键盘没有这些字符对应的按键,而这些字符又必须在一些文本中使用,因此采用一些替代方法。
言归正传,总得来说,thrgraph是C/C++ 为了照顾老一辈的"无产阶级革命家"而出现的,当时他们的条件极其艰苦,键盘上缺了很多键,无法输入以下九个字符:

     # \ ^ [ ] { } | ~

由此推才出现了 trigraph .

    换句话说,就是把上面的每个字符用其他三个字符来代替,替换规则如下:

 # ??= \ ??/ ^ ??' [ ??( ] ??) { ??< } ??> | ??! ~ ??-
            
      知道了原因,接下来看看让程序员摸不着头脑的错误代码(其实不是语法错误,而是程序运行的结果会让你大吃一惊)。
   情形1:  
#include <iostream>int main(){      int x = 1;      for( int i = 0; i < 100; ++i );        // What will the next line do? Increment???????????/        ++x;      std::cout << x;}
程序运行前,聪明的读者可以猜猜结果是多少?改变上面的程序,仅仅是一点点的一点点。
   情形2:
#include <iostream>int main(){      int x = 1;      for( int i = 0; i < 100; ++i )        // What will the next line do? Increment???????????/        ++x;      std::cout << x;}

最终的结果又是多少呢?
未运行前,你是否认为情形1的结果与情况2的结果一样,输出的结果都是101呢?细心的读者,会认为情形1的最终结果是2,而情形2的结果是101呢?
当然你认为的结果在有些编译器下确实是对的,这是因为有些编译器在默认情况将3元转义字符是禁止的。例如我在mac的g++编译器下编译上面的程序时它会提醒一个警告,如下:
warning: trigraph ??/ ignored, use -trigraphs to enable
即要使用trigraphs在编译时需要添加上-trigraphs参数。因此未加-trigraphs参数时,得出的结果与细心的读者预料的结果一致。而加上-trigraphs参数之后,那么结果你可能就得好好分析了。这里我们知道了注释行存在一个三元转义符"??/",其对应得符号为"\",而"\"将其下面得一行代码也作为注释,所以情形1实际的代码为:
#include <iostream>int main(){      int x = 1;      for( int i = 0; i < 100; ++i );      std::cout << x;}

情形2实际运行的代码为:
#include <iostream>int main(){      int x = 1;      for( int i = 0; i < 100; ++i )      std::cout << x;}
 因此情形1的输出为: 1 
 情形2的输出为:11111111111。。。1(一共为100个1)。
 
 或许你认为上面的错误出现的概率极其小,其实这是由于"?"与"/"只相差一个shift键而已。本来程序员是想打印出多个“???”,却在最后一个“?”松开了shift键,而将“???”变成了“??/”。最终的结果你也看到了吧!

下面举个例子来说明,下面是一个C++的简单程序:

#include <iostream>

using namespace std;

 

int main() {
   cout << "[]" << endl;

   return 0;
}

在当时键盘没有那九个符号的时候,那些程序员们就这么写:

??=include <iostream>

using namespace std;

 

int main() ??<
   cout << "??(??)" << endl;

   return 0;
??>

    以上内容转自百度上搜到的某个空间看到的,空间名叫"草"...

    将 trigraph 替换成对应的字符发生在预处理之前,因此 trigraph 可以在源码中的任何位置都可以用,包括字符串内,函数体开头,预处理指令等。

    有人说,如果那我就是用一个字符串常量,其中包含了 "??=" 怎么办?比如说就是要打印两个问号接一个等号,怎么办?很简单,把它拆开写,为了打印出 "??=",我们把它拆成 "??" "=" 即可,因为 C/C++ 在处理字符串字面值的时候,会把相邻的多个字符串字面值合并成一个,而这个合并操作发生在 trigraph 替换之后,
下面的程序就可以正确的打印出两个问号和一个等号:

#include <iostream>

using namespace std;

 

int main() {
   cout << "??" "=" << endl;
}

实测中,
VC 8 不给任何提示将 trigraph 替换成对应字符,
GCC 4.0.3 则要求加上编译参数 -trigraphs 才会做相应转换。

虽然 GCC 的做法是不符合标准的,但是更加安全。
更详细的情况可以参考标准 2.3。

 

以下是从c语言参考课程里摘的一段话:

    C源程序的源字符集被包含在7位ASCII字符集中,但不是ISO 646-1983Invariant Code Set的子集。三字母(trigraph)序列允许C程序仅使用ISO(国际标准组织) Invariant Code Set编写。三字母是编译器用对应的标点字符替换的三字符序列(以两个问号开头)。你可以在C源文件中使用三字母,该源文件的字符集不能包含某些标点字符的方便图形表示。

 

    一个三字母总是作为单个源字符处理,在第一次转换阶段中,在识别字符串文字和字符常量中的转义字符之前进行三字母的转换。仅识别表1.1中列出的9个三字母,所有其它字符序列不作转换。

    字符转义序别\?防止类似三字母的字符序列被误解释(有关转义序列的信息,参见本章后面的“转义序列”)。例如,如果你试图用以下printf语句打印字符串What??!:

printf(“What??!\n");

打印的字符串What|,因为??!是一个三字母,它被|字符所替换。正确打印这个字符串的语句如下:

printf(What?\?!\n");

在这个printf语句中,在第二个问号之前加上一个反斜杠转义字符防止??!作为一个三字母的误解释。

    
    还有更多的2元、3元转义字符造成的一些莫名其妙的结果参见:http://gcc.gnu.org/ml/gcc/2003-05/msg01691.html
      
     最后,不得不提一下c++标准委员会已经在2009年对是否取消3元转义符号问题进行了投票,虽然没有完全取消三元转义字符,但是增加一些使用的限制。具体可参考:http://stackoverflow.com/questions/6855149/are-trigraph-substitutions-reverted-when-a-raw-string-is-created-through-concate中的解答。

PS:前面提到了EBCDIC码,而我们学习的基本上都是基于asc码的,那么他们之间的区别为什么呢?

  使用得最多的、最普遍的是ASCII字符编码, 即American Standard Code for Information Interchange, 如表1所示。

  从表中可以看到:

  每个字符是用7位基2码表示的, 其排列次序为b6b5b4b3b2b1b0, 在表中的b6b5b4为高位部分, b3b2b31b0为低位部分。而一个字符在计算机内实际上用8位表示。正常情况下, 最高一位b7为 "0"。在需要奇偶校验时, 这一位可用于存放奇偶校验的值, 此时称这一位为校验位。

  表1 ASCII字符编码表

b6b5b4
000   001   010   011   100   101   110   111b3b2b1b0
-
0 0 0 0 
 0 0 0 1 
 0 0 1 0 
 0 0 1 1
 0 1 0 0 
 0 1 0 1
 0 1 1 0
 0 1 1 1 
 1 0 0 0 
 1 0 0 1
 1 0 1 0
 1 0 1 1
 1 1 0 0
 1 1 0 1
 1 1 1 0
 1 1 1 1
NUL   DLE   SP    0    @    P    、   p
  SOH   DC1   !     1    A    Q    a    q
  STX   DC2   "     2    B    R    b    r
  ETX   DC3   #     3    C    S    c   s
  EOT   DC4   $    4    D    T    d    t
  ENQ   NAK   %    5    E    U    e   u
  ACK   SYN   &     6    F    V    f   v
  BEL   ETB   '     7    G    W    g   w
  BS    CAN   (     8    H    X    h   x
  HT    EM   )    9    I    Y    I   y
  LF    SUB    *    :    J    Z    j   z
  VT   ESC   +    ;    K    [    k   {
  FF   FS    ,    <    L    \    l   |
  CR    GS    -    =    M    ]    m   }
  SO    RS    .    >    N    ↑    m   ~
  SI    US    /    ?    O     -    o    DEL

  ASCII是128个字符组成的字符集。其中编码值0-31不对应任何可印刷(或称有字形)字符, 通常称它们为控制字符, 用于通信中的通信控制或对计算机设备的功能控制。编码值为32的是空格(或间隔)字符SP。编码值为127的是删除控制DEL码。其余的94个字符称为可印刷字符,有人把空格也计入可印刷字符时,则称有95个可印刷字符。请注意, 这种字符编码中有如下两个规律:

  (1)字符0-9这10个数字符的高3位编码为011, 低4 位为000-1001。当去掉高3位的值时, 低4位正好是二进制形式的0-9。这既满足正常的排序关系, 又有利于完成ASCII码与二进制码之间的类型转换。

  (2)英文字母的编码值满足正常的字母排序关系, 且大、小写英文字母编码的对应关系相当简便, 差别仅表现在b5一位的值为0或1, 有利于大、小写字母之间的编码变换。

  另有一种字符编码,是主要用在IBM计算机中的EBCDIC代码(Extended Binary Coded Decimal Interchange Code)。它采用8位码, 有256个编码状态, 但只选用其中一部分。0-9十个数字符的高4位编码为1111, 低4位仍为0000-1001。大、小写英文字母的编码同样满足正常的排序要求, 而且有简单的对应关系, 即同一个字母的大小写的编码值仅最高的第二位的值不同, 易于识别与变换。

    
0 0
原创粉丝点击