PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  普通的linux 和 普通的windows。
  VS2015 和 GCC 7.0

前言


  曾记得,我在(https://blog.csdn.net/u011728480/article/details/100277582 《数与计算机 (编码、原码、反码、补码、移码、IEEE 754、定点数、浮点数)》)里面说过,计算机里面存储了数值和符号。数值包含定点数和浮点数。符号包含文字及其他符号。

  那符号在计算机中是怎么表示的呢?这里首先就要引出一个概念叫做字符集,就是“人为”记录归纳的某种文字或者符号在这个集合中的索引(例如:Ascii 中的’a’ 的索引为97)。有了这个字符集合后,我们怎么在计算机中表示这个字符集合呢?毕竟我们设计这个字符集合就是为了在计算机里面显示相关的符号的。这里我们就要引出一个字符编码的概念,就是在计算机中怎么表示相关的符号(例如:Ascii 我们发现基本的字符集只规定了128个。于是我们用一个字节来标识这个字符集中的字符即可。)。注意这里提到的Ascii即可指字符编码也可指字符集,至少我是这样理解的。

  一直以来,我反正只是大概模糊的记得,ascii 代表字母及其他基本字符。 gb2312/gbk/gb18030/big5 … 代表的就是多字节的中文及其他字符。unicode 和 utf-8 可以代表世界上大部分国家及地区的字符符号,同时unicode是两字节的,utf-8是多字节的(注意,关于unicode的说法其实有点小毛病,详见下文)。我早就想详细的了解和记录一下这些说法的含义,但是一直没有一个较好的机会。

  最近做的一个项目里面,要在内存里面查找和对比中文(c++ 里面存的中文,lua要去找和对比),这NM可把我苦惨了,于是我一狠心,就要把这个问题搞明白,不求全部搞清楚,但求我能够怎么解决这个问题,以及以后遇到这些问题怎么处理。

  下面是一些常见及不常见短语说明:

  1. UNICODE(The Unicode Consortium(统一码联盟,是大公司组合的联盟),一种字符集)
  2. UCS(Universal Multiple Octet Coded Character Set(通用多字符编码集,是ISO标准委员会提出的),一种字符集)
  3. UTF/UTF-8/UTF-16(一种基于UNICODE的字符编码格式,UTF是UCS Transfer Format的简写)
  4. GB2312/GBK/GB18030(全国信息技术标准化技术委员会出版的3个版本的字符集及字符编码格式)
  5. ASCII(American Standard Code for Information Interchange,常见的基本英文编码)
  6. DBCS(Double-byte character set)
  7. ANSI(不知道是什么的简写,但是只会出现在windows平台)
  8. 本地化/国际化(本地化和国际化是一个比较坑的概念,本地化可以简单理解为把文字和符号显示给对应区域的人。 国际化就是把文字和符号显示给尽量多的人。我也不知道这样翻译对不对QAQ)

常见的字符集及对应的字符编码规则说明


  字符集是存放的人为定义的一个字符索引集合。 字符编码是考虑怎么把这个字符集合在计算机中表示出来。

常见中文字符集及字符编码(GB2312/GBK/GB18030)

  关于GB2312/GBK/GB18030的详细说明大家可以去网上找找资料详细了解,他们有许多的历史因素在里面。我这里就只做简单的说明。

  首先GB2312/GBK/GB18030是三个国标的简称。是全国信息技术标准化技术委员会参考或者说是对接ISO提出的字符集/字符编码方法,然后出版的适合中国特色的字符集/字符编码标准。注意这里的GB2312/GBK/GB18030既可以称作字符集也可以称作字符编码,我们好像常用是把这个三个当做字符编码,但是没有强调字符集是什么,所以我觉得这个是三个即是字符集又是字符编码。下面对这三个字符编码规则进行简单的说明,这些规则里面可能有些历史原因小故事在里面,感兴趣的人去网上找找看,我这里不做无用功了。

  GB2312是我国第一个字符集/字符编码。其使用2个字节代表一个汉字,而且为了兼容Ascii,约定两个编码字符都必须大于0xA0(每个字节都大于127,可以区分出Ascii与GB2312)。也就是说,GB2312的编码范围为:0xA1A1~0xF7FE。而且由于标注出版的比较早,里面只包含了常见的汉字和非汉字内容。

  GBK是对GB2312的扩展。同样也是使用2个字节代表一个汉字。首先GBK原封不动的继承了GB2312的编码,同时编码范围由0xA1A1~0xF7FE 扩展到0x8140~0xFEFE。多余GB2312的这些编码,又添加了一些cjk的汉字和符号,同时也提供了自定义文字区域编码的。

  GB18030是2005年出的最新的中文字符集/中文字符编码。它是变长字节编码方式,和utf系列很像。下面进行简略的说明:

  1. 1字节,0x00~0x7F 兼容Ascii
  2. 2字节,0x8140~0xFEFE 兼容GBK
  3. 4字节,0x81308130~0xFE39FE39 存放其他文字和符号,例如我国的少数民族的文字、繁体汉字、日韩汉字等等。

  这里多说一句,采用变长编码的原因是节约字符存储空间或者说是为了节约网络传输带宽。

常见的Unicode字符集与UTF系列编码

  上面我们介绍了中文的字符集及字符编码,可以想象的是,非中文,非英文地区的人,也会做和我们同样的事情,他们会定义适合他们自己的字符集,并定义适合他们自己的字符编码。那就直接炸裂了,因为每个地区都有自己的字符集和字符编码,非常不适合各个地方的人们文字交流。与此同时,网络使得各个地方的人们交流更加的频繁,于是有些人就不爽了,想定义一个字符集来包含全世界的所有字符,这样人们交流的时候就不需要对字符进行专门的转码。

  于是国际标准委员会和一个叫做统一码联盟的组织分别起草了一个字符集,分别是UCS 和 UNICODE。后面考虑到大家都是做的同样的事情,于是两个字符集合并了,叫做UCS/UNICODE。我们常见的是UCS-2/UNICODE。这个字符集里面包含了全世界大部分的文字和符号。其表示范围大概是0x000000 到 0x10FFFF。 UNICODE 字符索引一般表示为U+0x00AAAA

  在定义UCS/UNICODE这个超大字符集后,肯定想定义一个字符编码才符合这些组织的身份。于是产生了UTF字符编码系列的格式。我们常见的就是UTF-8/UTF-16 BE/UTF-16 LE/UTF-32 BE/UTF-32 LE格式。

  UTF-32简要说明:直接用4个字节表示UNICODE字符串, 例如索引U+0xABCDEFAA 就表示为0xABCDEFAA(BE 大端) 或者 0xAAEFCDAB(LE 小端)。

  UTF-16简要说明(windows常用编码,与UTF-32一样有类似的字节序存在):

  1. U+0x0000 到 U+0xFFFF 用2个字节表示。
  2. U+0x1 0000 到 U+0x10 FFFF 用4个字节表示。

  下面对UTF-8进行简要的说明:

  1. 1字节,0000/0000-0000/007F(hex), 二进制填充方式:0xxx xxxx(binary)
  2. 2字节,0000/0080-0000/07FF(hex), 二进制填充方式:110x xxxx/10xx xxxx(binary)
  3. 3字节,0000/0800-0000/7FFF(hex), 二进制填充方式:1110 xxxx/10xx xxxx/10xx xxxx(binary)
  4. 4字节,0001/0000-0010/FFFF(hex), 二进制填充方式:1111 0xxx/10xx xxxx/10xx xxxx/10xx xxxx(binary)

  对应的编码范围是:

  1. 0~127
  2. 128~2047
  3. 2048~65535
  4. 65536以上

  UTF-8的实现方式就是查出字符索引:U+0xABCD(U+43981) ,可以看到落在的编码范围是3字节范围,也就是2048~65535。于是我们看到的二进制还有16个位置,恰好,我们的编码的二进制也是16个。从左到右依次放入对应位置的x即可。0xABCD二进制为:1010 1011 1100 1101, 对应的UTF-8编码为: 1110 (1010)/10(10) (1111)/10(00) (1101)

本地化和国际化


  上面我们介绍了两个系列的字符集和对应常用的字符编码。GBXXX系列是CJK区域的多字节编码,UNICDOE/UTF系列是全球大多数通用字符集及编码。那么为了我们发布的计算机文件能够在全世界方便的使用,我们有两种方案:

  1. 使用区域性多字节编码,例如我们发布的文件,携带多种区域性字符编码文件(GBXXX/阿拉伯的编码等等),在不同地区的电脑上,根据系统的地区(win和linux都有,很重要,设置区域),使用不同的区域字符编码文件进行显示。
  2. 直接使用UNICDOE/UTF系列,全球通用。

  看起来,直接使用UNICDOE/UTF系列就完事儿了,花里胡哨,弄那么多。其实不然,你看了UTF-8,对我们中文区来说不公平,因为大部分都是3字节,而对于Ascii区域来说,他们的UTF-8,大部分都是1字节。这NM就坑了撒,难道我大中华的硬盘或者带宽就无限了?其次,可能有些我们可以在GB系列里面定义的偏门字符内容,可能UNICODE里面没有,对于一个足够大的市场来说,如果连他们的文字符号都表示不完,那还玩个D。于是也需要有GB系列这种区域性的来补充,换句话说,就是看实际应用。这就是软件本地化和国际化的意义,里面最要命的就是字符问题。

生活中常见的几个有趣小实验(猜到就让你嘿嘿嘿)


  下面我们做一些比较有趣,而且常见的小实验。

VS的Unicode字符集 和 多字符集选项(cl.exe)

  在我们编程的时候,特别是要涉及中文编程的时候,很多时候需要和这个选项打交道,也就是如图。那么这两个选项有啥区别呢?请听我慢慢道来。

  这个选项的主要作用是用来帮助 cl.exe 确认启用什么样的Api,也就是我们常说的W结尾的还是A结尾的Api。下面我们用下面的小程序来实验一波。

#include <cstdio>
#include <windows.h>
int main(int argc, char * argv[]) {const char *_str = "卧槽";printf("_str's mem = %x %x %x %x\n", 0xFF & _str[0], 0xFF & _str[1], 0xFF & _str[2], 0xFF & _str[3]);const wchar_t *_str_1 = L"卧槽";printf("_str's mem = %x %x\n", 0xFFFF & _str_1[0], 0xFFFF & _str_1[1]);//MessageBoxW(NULL, L"卧槽", L"U", MB_OK);//MessageBoxA(NULL, "卧槽", "M", MB_OK);system("pause");return 0;
}

  运行以上代码我们可以得到下图的内容。

  然后我们根据以上的内容,通过二进制编辑器,构造了两个txt文件。然后通过vs code 不同解码下打开。才能够得到正确文字内容。

  下面我们来解释Window Api中 A系列和W系列的区别。 例如在MessageBoxW(NULL, L"卧槽", L"U", MB_OK)和 MessageBoxA(NULL, “卧槽”, “M”, MB_OK)中,我们传入的参数一个是char *的,一个是wchar_t *,通过我们打印,可以发现对应的内存数据是完全不一样的,也就是说对应的文字编码是完全不同的。A系列对应的GB18030编码(多字节,区域编码,不同地区,可能就不是gb系列的编码了),W系列对应的UTF-16 BE编码(UNICODE)。那么,它们代表啥意思呢?

  如果我们用A系列的Api,那么就是用的多字节编码,也就是对应的本地区域编码,在我们这个CJK区域,能够正常显示文字,但是如果不在我们CJK区域的话,极有可能就出现乱码。也就是说,通过A系列弄出来的程序,很有可能就只能够在我们CJK区域使用,其他区域可能需要用源代码,在其他对应区域的VS编译一下,才能够正常使用程序。

  如果我们用W系列的Api,那么用的就是UTF-16 BE编码,由于UNICODE是针对全球大多数语言来做的一个字符集,那么意味着,我们这个程序只需要编译一次,把二进制分发到全世界大多数区域也能够正常使用的。

GCC的-finput-charset/-fexec-charset=gbk选项

  首先GCC的默认把源文件用UTF-8解码,如果遇到不支持的字符,需要使用-finput-charset来帮助才行。然后,我们分别带和不带-fexec-charset=gbk编译如下程序,并运行。

#include <cstdio>int main(int argc, char * argv[]) {const char *_str = "卧槽";printf("_str's mem = %x %x %x %x %x %x\n", 0xFF & _str[0], 0xFF & _str[1], 0xFF & _str[2], 0xFF & _str[3], 0xFF & _str[4], 0xFF & _str[5]);const wchar_t *_str_1 = L"卧槽";printf("_str's mem = %x %x\n", 0xFFFF & _str_1[0], 0xFFFF & _str_1[1]);return 0;
}

  得到如图的结果。

  从图片结果我们可以知道,GCC对待字符串的方式和CL.exe不是很一致,但是通过传入相关参数,即可得到同样的结果。这里强调一下,-fexec-charset 参数相当于cl.exe的解码设置,类似上文vs选项,我们可以知道GCC的多字节默认编码是UTF-8。同时,GCC和CL.exe一样,对于char_t类型,都是使用的UTF-16 BE格式。

  这里,我们通过如图的编码输出,手动来转换一下UTF-8和UNICODE,验证我们之前说的规则是否正确。

  1. “卧” 对应的是U+005367,十进制为U+21351,二进制U+0101 0011 0110 0111,根据区域值,是3字节模式,对应填入得到UTF-8二进制 1110 0101 1000 1101 1010 0111,十六进制为0xE58DA7
  2. “槽” 对应的是U+0069FD,十进制为U+27133,二进制U+0110 1001 1111 1101‬,根据区域值,是3字节模式,对应填入得到UTF-8二进制 1110 0110 1010 0111 1011 1101,十六进制为0xE6A7BD
  3. 参考模式:1110 xxxx/10xx xxxx/10xx xxxx(binary)

  这里,我们发现,算出来的值,完全和我们的前面说的规则一样。

VS Debug模式下的“烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫” 看似搞笑行为

  首先,我们在vs的debug模式下,运行如下程序。

#include <cstdio>int main(int argc, char * argv[]) {const char *_str = "卧槽";printf("_str's mem = %x %x %x %x\n", 0xFF & _str[0], 0xFF & _str[1], 0xFF & _str[2], 0xFF & _str[3]);const wchar_t *_str_1 = L"卧槽";printf("_str's mem = %x %x\n", 0xFFFF & _str_1[0], 0xFFFF & _str_1[1]);char _test[10];for (int i = 0; i < 10; i++)printf("%x ", 0xFF&_test[i]);printf("\n%s", _test);return 0;
}

  得到如图的结果。

  其实是由于vs 在debug模式下,会把我们为初始化的内存初始化为0xCC。而0xCCCC恰好是“烫”的GB18030编码,所以在我们CJK区域打印是“烫”,在其他区域可能是其他的字符。

后记


  其实到了这里,我已经解决了我想要解决的问题。因为我只要知道目标程序的内存中中文的具体编码(OD或者CE等等),然后我就可以进行我想要的文字查找。

  其实本文也解决了gcc生成的程序和cl.exe生成的程序字符串交换的问题。一个默认用的utf-8,一个是本地编码,对我们来说,就是GB系列。


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

字符集、字符编码、国际化、本地化简要总结(UNICODE/UTF/ASCII/GB2312/GBK/GB18030)相关推荐

  1. ASCII,unicode, utf8 ,big5 ,gb2312,gbk,gb18030等几种常用编码区别

    ASCII,unicode, utf8 ,big5 ,gb2312,gbk,gb18030等几种常用编码区别 最近老为编码问题而烦燥,下定决心一定要将其弄明白!本文主要总 结网上一些朋友提供的 asc ...

  2. php的内部方法编码方式,字符集字符编码以及PHP中的一些转码方法

    在重写一些老的项目的时候,有可能会遇到这种情况 ¾ÍÒµÖ¸µ¼ÖÐÐÄ2010Ä꺮¼Ù·Å¼Ù֪ͨ �����˵�λ������� ???????? 这就是传说中的乱码,要想处理乱码,得先了 ...

  3. 关于字符编码,你所需要知道的(ASCII,Unicode,Utf-8,GB2312…)

    字符编码的问题看似很小,经常被技术人员忽视,但是很容易导致一些莫名其妙的问题.这里总结了一下字符编码的一些普及性的知识,希望对大家有所帮助. 还是得从ASCII码说起 说到字符编码,不得不说ASCII ...

  4. 字符编码详解及由来(UNICODE,UTF-8,GBK)

    字符编码详解及由来(UNICODE,UTF-8,GBK) 各种字符编码方式详解及由来(ANSI,UNICODE,UTF-8,GB2312,GBK) - 2009-01-29 09:53     一直对 ...

  5. 深入理解-字符编码ASCII,GB2312,GBK,Unicode,UTF-8

    字符编码 简介 起初再考虑写不写这篇文章,感觉这篇文章比较枯燥乏味,而且自己感觉也没理解的太透彻,就把理解的记录下来,所以这是纪念版的 前方高能,非战斗人员请迅速撤离,我要开始装逼了. Go hard ...

  6. 大端小端,ascii,unicode,utf8,utf16,utf32,gb2312,gbk,gb18030等字符编码问题

    字符编码的问题让我困惑了好久的一段时间,其实简单的想,字符编码没有什么东西的,可是想真弄明白还是花去了我一点时间,前端时间写了一个简单的log程序,主要的工作就是支持系统运行时输出日志信息,同时允许定 ...

  7. Qt中的字符编码转换:UTF8、Unicode、GBK、ASCII、16进制字符、16进制数值

    文章目录 前言 简述 ASCII GBK Unicode UTF-8 应用场景 开发环境 编码转换 16进制数值转换为16进制字符 16进制数值转化为字符串 16进制字符串转换为Unicode字符串 ...

  8. 细说ASCII、GB2312/GBK/GB18030、Unicode、UTF-8/UTF-16/UTF-32编码

    参考: <编码标准-GB2312 GBK GB18030> <字符编码笔记:ASCII,Unicode 和 UTF-8> <字体编辑用中日韩汉字Unicode编码表> ...

  9. GB2312, GBK, GB18030 这几种字符集主要的区别

    转载自:http://www.zhihu.com/question/19677619 1 GB2312-80 GB 2312 或 GB 2312-80 是中国国家标准简体中文字符集,全称<信息交 ...

最新文章

  1. ResultSet转List
  2. python合并excel文件关键字_python合并多个excel文件的示例
  3. Windows、Linux和MAC的CR, LF, CR/LF换行符
  4. 十三、CSS 3新特性详解(一)——属性、结构伪类、伪元素选择器,nth-child与nth-of-type区别,2D rotate,calc函数、滤镜filter、过渡transition
  5. 互联网日报 | 京东开启最大规模校招;特斯拉西部首个交付中心在蓉投入使用;嫦娥五号上升器点火起飞...
  6. 摩拜免押金;滴滴外卖订单骤降;小米最快本周 IPO | CSDN极客头条
  7. 网建短信通 使用java调用API发送短信时总是返回-41,关键的关键,仅在于一个问号:(
  8. Elasticsearch Index Template(索引模板)
  9. 4.java中的常见语句
  10. PostgreSQL13.1-CN-v1.0中文手册.chm下载
  11. win10强制删除文件夹(“你需要来自XXX的权限才能对此文件夹进行更改”的解决方法)
  12. 风生水起的VR直播丨VR直播市场何时迎来爆发期?
  13. [渝粤教育] 四川轻化工大学 化工设备机械基础 参考 资料
  14. WinCE快捷方式浅析
  15. 关于webservice服务在springboot项目中的开发的介绍
  16. StarUML使用说明
  17. ArcGIS基于C#.NET的二次开发
  18. 清华大学计算机学院2021拟录取,清华大学、上海交通大学公示拟录取2021级研究生名单了...
  19. 西安互联网公司防坑指南
  20. Nacos 简介与 本地调试环境搭建

热门文章

  1. 设置 IDEA走easyconnect代理
  2. python解决租房问题_高德API+Python帮你解决租房问题
  3. 运算符的优先级(从高到低)
  4. 算法设计-利用栈判别表达式中的括弧是否配对
  5. iOS直播实用篇(手把手教)
  6. Java小程序木叶村_恋爱球滚动的天空
  7. 物联网周刊(第 6 期):开源硬件公司 Adafruit
  8. 积分无法积,用估值后再使用连续函数介值定理_20160430
  9. 计算机人民币货币符号是什么,人民币货币符号-人民币符号究竟是什么?yen;;还是¥? 爱问知识人...
  10. PageRank背后的数学