c/c++ 未知的陷阱——2元、3元转义字符
在编写程序时需要时时提防编译器背着我们干一些没有通知我们做的事,下面列举转义字符对程序的影响。
首先列出二字符组和三字符组对应的意思。
二元字符 等价字符
<: [
:> ]
<% {
%> }
%:或者%% #
三元字符 等价字符
??= #
??/ \
??' ^
??( [
??) ]
??! |
??< {
??> }
??- ~
值得庆幸的是,二元字符或者三元字符转义之后不能再与二元字符或者三元字符结合,要不然程序员可能会疯了。闲话少说,那么为什么会c++标准允许出现这种情况呢?
根据 Intention to deprecate trigraphs in the next C++ Standard可以知道这有两个原因:
1.由于类似“#\”等这些字符在EBCDIC的代码页中用来区分代码点的。在所有的EBCDIC的代码页中使用“?”和"="不会分割代码页,而会共享同一个代码点。
2.其次是因为一些国际化的键盘设置原因,有些键盘没有这些字符对应的按键,而这些字符又必须在一些文本中使用,因此采用一些替代方法。
知道了原因,接下来看看让程序员摸不着头脑的错误代码(其实不是语法错误,而是程序运行的结果会让你大吃一惊)。
情形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键,而将“???”变成了“??/”。最终的结果你也看到了吧!
还有更多的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 111
b3b2b1b0
-
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)英文字母的编码值满足正常的字母排序关系, 且大、小写英文字母编码的对应关
补充:软件开发 , C++ ,