逆向——字符与字符串,中文字符GB2312编码由来

发布时间 2023-04-02 21:07:47作者: bonelee

字符与字符串

在之前的课程中我们了解到变量的定义决定两个事情,第一是决定存储的数据宽度,第二是决定了存储的数据格式,那么我们来看下下面的代码:

int a = 123; // 变量x,数据宽度为4个字节,里面存储的是补码(在计算机系统中,数值一律用补码来存储)
int float b = 123.4F; // IEEE编码(浮点)
int c = 'A'; // 那这个存储的是啥?

 

我们在代码中写了变量c,但是它最终存储进去的是啥呢?我们看下反汇编:

images/download/attachments/12714021/image2021-2-19_0-51-43.png

在这里'A'存储的时候变成了0x41,这是为什么?因为它是一个字符,这里需要注意在变量定义赋值时,赋值字符需要加上单引号。

在内存中所有东西最终都会变成0和1,A对应0x41,那么可以大胆猜测一下B就是对应0x42,当我们使用字符存储到内存时,字符自然是没办法存储到内存中的,这时候就有了一个字符对应的表:ASCII表(美国标准信息交换代码)

images/download/attachments/12714021/image2021-2-19_0-56-21.png

这张表有128个符号,都是比较常用的,那也就是说在我们赋值A给变量时,编译器会去这张表中寻找大写的A,如上图所示A对应10进制为65,转为16进制就是0x41。

值得注意的是在这张表中最多也就是占用一个字节的宽度,所以我们完全没有必要使用int来存储字符,可以选择char,例如:char a = 'A';

在很多书中会描述char就是用来存储字符的,这是一个错误的说法,数据的存储是由使用者决定的而不是计算机。

C语言会自带很多函数提供我们使用,我们想要在控制台中输出一个字符,可以使用putchar这个函数:

putchar(65);
putchar('A');

如上代码就是输出一个字符A,这样我们就了解了这个函数的运行本质:将对应数从ASCII表中查出画在(打印)控制台上;需要注意该函数一次只能打印一个字符。

除了这个函数外还有一个打印输出的函数:printf,它就可以一次性打印多个字符。

#include <stdio.h>
 
void main()
{
printf("Hello World!\n");
 
int x = 0xFFFFFFFF;
printf("%d %u %x\n",x,x,x);
// -1 4294967295 ffffffff
 
float f = 3.1415F;
printf("%6.2f\n",f);
// 3.14
}

使用printf函数需要在代码开头写#include <stdio.h>,三个printf分别表示输出字符串、整数、浮点数。

有心之人可能会发现在使用printf函数时,有好几个百分号的东西,这就叫做占位符,一共分为如下这几种:

%d 有符号数形式打印

%u 无符号形式打印

%x 16进制形式打印

%{x.y}f 打印浮点数 x标志打印总长度 y 代表小数点后长度

看过示例代码之后,我们就知道了字符串本质上就是一堆字符连续串在一块,观察一下反汇编代码:

images/download/attachments/12714021/image2021-2-19_1-19-37.png

这时候就存在一个问题,printf为什么会知道打印完最后一个感叹号之后就不打印了呢?这是因为在这一串字符串后存在着一个00,当printf看见之后就会停止打印了,也就是说字符串的结束标志在内存中是00。

我们想使用char类型来存储字符串,就需要用到数组,例如:

char buffer[20] = "Hello World!";
printf("%s\n",buffer);

这就表示buffer可以存储20个字符,%s是占位符,表示以字符串形式打印,关于数组的细节后面的课程中会学习到,这里了解一下即可。

中文字符

在之前了解到ASCII码表中,我们并没有发现存在中文,那么如何在计算机中存储中文?这时候我们需要了解一个新的表:拓展ASCII码表 (EASCII)

images/download/attachments/12714021/image2021-2-19_1-27-21.png

该表十进制值从128到255,但是这些也没办法满足我们中文的需求,所以天朝专家把那些127号后的奇异符号们 (即EASCII)取消掉,规定:一个小于127的字符意义与原来相同,但是两个大于127的字符连在一起时,就表示一个汉字,这样我们就可以组合出大约7000多个简体汉字了。

在这些编码里,连在ASCII里本来就有的数字、标点、字母都统统编了两个字节长的编码,这就是常说的”全角“字符,而原来在127号以下的那些就叫”半角“字符了,上述编码规则就是GB2312或GB2312-80

GB2312或GB2312-80,两种编码可能使用相同的数字代表两个不同的符号,或者使用相同的数字代表不同的符号,这种编码方式有很大的弊端,当此种编码方式的数据在其他国家使用的时候,如果其他国家使用类似的编码规则,那么数据就会失去原本的意义

而前辈们早就发现了这种情况,因此Unicode编码就是为了解决这个问题才出现的。

在C语言中写入中文与其他字符串没区别,但我们了解过GB2312或GB2312-80编码规则后,就要知道一个中文代表2个字节:

char buffer[20] = "中国";
printf("%s\n",buffer);

images/download/attachments/12714021/image2021-2-19_1-33-38.png