详谈字符编码[二]代码页和一个乱码案例

摘要:
代码页,正是这些有历史感的概念之一。这篇博文带你了解代码页和当前Windows对Unicode和ANSI编码的支持情况,末尾分析了一个乱码的案例,出于某知名软件,你一定不想错过。但是可想而至,这二者一旦不匹配就会出现乱码。不同语言国家的Windows编码都不一样,因此微软使用了代码页来解释字符编码,比如简体中文版的Windows默认代码页就是GBK,这意味着默认使用GBK来解释字符串,所以能显示中文是必然的,显示其他的语言是乱码也是必然的。

上一篇关于字符编码的随笔介绍了编码,输入码,机内码,字形码,字形库等概念。除此之外,还有一些其他的概念我们不得不了解,它们已经不属于现在,但是却时常影响着现在。代码页,正是这些有历史感的概念之一。这篇博文带你了解代码页和当前Windows对Unicode和ANSI编码的支持情况,末尾分析了一个乱码的案例,出于某知名软件,你一定不想错过。

Windows的默认编码?

偶尔在知乎看到这样的问题:为什么中文Windows选择GBK作为默认编码?其实会有这样的误解也难怪,为什么这么说呢?大家都从控制台的Helloworld开始,后来想要输出中文时自然先想到printf("你好,世界");运行发现真的出现了中文,仿佛英文和中文没什么区别,世界很美好的感觉。但学习更多之后发现Windows下的strlen("你好,世界")的值竟然是10(严格说是MinGW下使用GBK作源编码时或者使用VS时才是10),第一次感觉到了英文字母与汉字的区别,于是我们去寻找原因,终于得知GBK编码之类的各种编码,也知道了代码页这个令人疑惑的名词。你好世界,世界却是灰色的。

实际上微软早就声明:“UTF-16Little-endian是Microsoft以及Windows操作系统中的编码标准”。在Windows2000以前的操作系统上,内码的编码是和语言相关的(ANSI编码)。那时候简体中文版的Windows使用GBK,所有的中文的软件中的字符串也都是GBK,所以在window上运行也不会乱码。但是可想而至,这二者一旦不匹配就会出现乱码。不同语言国家的Windows编码都不一样,因此微软使用了代码页来解释字符编码,比如简体中文版的Windows默认代码页就是GBK,这意味着默认使用GBK来解释字符串,所以能显示中文是必然的,显示其他的语言(比如日语)是乱码也是必然的。

Windows2000之后(严格说是Windows NT 3.1之后)默认使用UTF-16作为编码标准。这是什么意思呢,意思就是全世界的字符你都可以处理,如果安装了相应的字体,你还可以显示全部字符,如果安装了相关输入法你还可以输入任意一种语言。但是哪些不是UTF-16编码的程序还能在新平台下运行吗?可以的,在这一方面微软还是负责任的,毕竟当初是自己提出的代码页方案,不能把软件开发商们都得罪了。所以直到今天(Windows 10)微软都是兼容二者,但是提倡使用UTF-16。那么Windows的默认编码是什么呢?事实是最好不要使用”默认编码“这个词,因为根本没有什么默认的编码(你可以决定使用任何一种编码,只不过别人不认识而已),推荐使用官方的说法“编码标准”,而且微软的编码标准是UTF-16L。

之所以很多初学者有误解,是因为一开始的程序基本都是控制台程序,而控制台的默认代码页确实是GBK。使用chcp命令可以查看当前代码页,可以看到回显Active code page: 936,这正是代表GBK。可以使用命令“chcp 65001”切换到UTF-8。控制台为了兼容性默认代码页是936,不代表Windows的编码标准是GBK,下面的试验都在对话框上显示,因为这是最简单的检验GUI编码方式的方法。

Windows对两种机制的兼容

那么具体Windows是怎样同时兼容二者:既支持UTF-16,又可以使用ANSI编码的呢?使用一个MessageBox做一下试验。

1 #include<windows.h>
2 intmain() {
3     MessageBox(NULL, L"你好,世界", L"你好,世界", 0);
4     return 0;
5 }

效果是下面图1这样

详谈字符编码[二]代码页和一个乱码案例第1张详谈字符编码[二]代码页和一个乱码案例第2张

图1 图2

详谈字符编码[二]代码页和一个乱码案例第3张

图3

但大家都知道,这里是使用了(如图2),调用MessageBox()实际上是调用了MessageBoxW()MessageBoxW()的参数是wchar_t类型的,wchar_t*的字符串字面量一般被实现成UTF-16编码。与之对应的是MessageBoxA()MessageBoxA()接受的参数是char*类型的,char*的字符串字面量被实现成ANSI编码。我说的字符串字面量被实现成某某编码是什么意思呢?用图片解释一下(图3),两个字符串虽然都是“你好,世界”但是运行时的样子完全不同。要强调机器只认识二进制,所以对机器来说这俩个字符串没有任何相同点。我们如果就是调用MassageBoxA(),传入char*会显示什么呢。

#include<windows.h>
intmain() {
    MessageBoxA(NULL, "你好,世界", "你好,世界", 0);
    return 0;
}

很意外,结果竟然和图1完全一样。两个完全不同的二进制串竟然显示了相同的正确结果。其实这就是所谓的Windows兼容两种编码(UTF-16与ANSI编码),虽然推荐使用UTF-16但是,使用GBK也能正常在Windows的GUI中显示,但是你的应用程序已经被Windows归类为“非Unicode程序”。这里大家可以打开控制面板--时钟语言和区域--区域--管理,可以找到一个设置项:非Unicode程序的语言。可以选择中文或者其他语言,那它有什么影响呢?你不妨尝试一下改成日语或者韩语什么的,再运行第二段代码,你就能看到乱码了。但是除此之外几乎感受不到影响,程序们还是正常的运行着,没有出现乱码,这说明现在的绝大多数程序都是使用的UTF-16编码,所以这一个设置对他们根本没有影响。

但也并不绝对,在笔者把这一项设置修改为“日语(日本)”一周后(我已经忘了自己没有改回来,因为确实没有什么影响)看到一个奇怪的文件夹,他的名字是:ムクタラマツヤリ。是哪一个程序搞出了这样的乱码呢?文件夹名字原来是什么呢(不要指望这是日语,这只是日语字符组成的乱码,不能看出含义)?下一节我们来分析这个例子,揭开这个还在使用ANSI编码的程序的羞耻的面纱。

一个乱码的例子分析

日本的ANSI编码是Shift_JIS,它是在有Unicode之前日本国内计算机的编码方式。可想而知,之所以出现ムクタラマツヤリ这一段乱码,就是因为我把非Unicode程序的语言设定成了日语(日本),所以导致某个想要用GBK字符串命名文件夹的程序创建了乱码的名称。现在能够查到这些字符的Unicode编码(复制粘贴后,他已经变成了Unicode),所以把Unicode转换成的Shift_JIS二进制串解释为GBK就得到文件夹本来的名字了。

ff fe 91 ff 78 ff 80 ff 97 ff 8f ff 82 ff 94 ff 98 ff<--这是UTF16小端编码,开头的0xfffe是BOM,不知道BOM是什么的可以查看详解字符编码[一]

d1 b8 c0 d7 cf c2 d4 d8<--这是对应的Shift_JIS

详谈字符编码[二]代码页和一个乱码案例第4张

把上面的二进制翻译成GBK就是答案:迅雷下载

我用的正是最新版本的迅雷:9.1.41.914。迅雷的一个小Bug就这样被我发现了。

日志:在把非Unicode程序的语言改为日语后,只有一个MFC的上古程序和最新版的迅雷出现了乱码,2017年10月9日。

下一篇会介绍C/C++程序避免乱码的方法并介绍怎样在Java中处理UTF-16的代理对。喜欢请给个推荐,再见。

免责声明:文章转载自《详谈字符编码[二]代码页和一个乱码案例》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Qt中indexOf()和lastIndexOf()查找字符串位置网路编程(网站源码查看器)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python-re正则、jsonpath返回值提取

re """ re.match 从头开始匹配 re.match(pattern, string, flags=0) 只匹配第一个,返回对象 先判断赋值的变量,加.group()返回值 re.search 匹配包含 re.search(pattern, string, flags=0) 只匹配一个,返回对象...

Unicode(UTF-8, UTF-16)令人混淆的概念

为啥需要Unicode       我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数字.你肯定不能想怎么转换就怎么转,必须得有...

编一程序,将两个字符串连接起来,不要用strcat函数

编一程序,将两个字符串连接起来,不要用strcat函数 【答案解析】 直接将s2中的字符逐个拷贝到s1的末尾即可,用户需要保证s1中能存的下s2中的字符 获取s1末尾的位置 将s2中的字符逐个拷贝到s1中 【代码实现】 #include<stdio.h> int main() { char s1[100] = {0}; char s2...

request和response简介

Tomcat收到客户端的http请求,会针对每一次请求,分别创建一个代表请求的request对象、和代表响应的response对象。 既然request对象代表http请求,那么我们获取浏览器提交过来的数据,找request对象即可。response对象代表http响应,那么我们向浏览器输出数据,找response对象即可。 http响应由状态行、实体内容...

sql server中字符串无法替换空格的问题

直接上代码: select case when 'workReport'=LTRIM(RTRIM(' workReport ')) then 'trim去空格成功' when 'workReport'=REPLACE(' workReport ',' ','') then 'replace去空格成功' when 'workReport'=REPLACE('...

正则表达式——RegExp零宽断言

正则表达式之中,支持某匹配对象的前面或者后面满足条件的匹配模式。这种匹配模式叫做零宽断言。 零宽断言的格式类似于(?exp)exp (?<=ing)ing表示匹配对象前面是ing的,ing对象如:singingdancing能匹配第一个 kiss(?=ing)表示匹配对象后面是ing的,内容是kiss对象的如:kissingkissed只能匹配第一个...