注:以下内容均来自开源学习组织DataWhale
1 虚拟地址空间
将内存视为一个数组,数组中的元素是由一个个字节组成,每个字节由一个唯一数字表示,这个数字称为地址,所有地址的集合称为虚拟地址空间。
2 Byte(字节)与bit(位)
进制转换:
对于 2 n 2^n 2n(二进制表示为1后面n个0)这样的二进制数字,快速转为十六进制的方法:先根据 n = i + 4 j n=i+4j n=i+4j计算i, j,最终十六进制表示为0x 2 i 2^i 2i后面加j个0。
例: 2 11 2^{11} 211 ; 11 = 3 + 2 × 4 11 =3+2\times4 11=3+2×4,i = 3, j = 2 对应十六进制为0x800
十进制与十六进制转换:辗转相除
3 字数据大小
字长为w位的机器,虚拟地址范围是 0 0 0到 2 w − 1 2^w-1 2w−1
例如32位机器,虚拟地址有 2 32 2^{32} 232个,每个地址是1byte,即总共 2 32 2^{32} 232bytes = 4GB
字长 | 地址空间 |
---|---|
w bit | 0 0 0 ~ 2 w − 1 2^w-1 2w−1 |
32 bit | 0 0 0 ~ 2 32 − 1 2^{32}-1 232−1, 4GB |
64 bit | 0 0 0 ~ 2 64 − 1 2^{64}-1 264−1, 16EB |
4 C语言中数据类型所占字节数大小
数据类型(有符号) | 数据类型(无符号) | 字节数(32位机器) | 字节数(64位机器) |
---|---|---|---|
[signed]char | unsigned char | 1 | 1 |
short | unsigned short | 2 | 2 |
int | unsigned | 4 | 4 |
long | unsigned long | 4 | 8 |
int32_t | uint32_t | 4 | 4 |
int64_t | uint64_t | 8 | 8 |
char * | 4 | 8 | |
float | 4 | 4 | |
double | 8 | 8 |
5 寻址和字节顺序
假设一个int类型变量x(0x01234567),占4个字节,地址位于0x100处,因此变量x存储地址为0x100,0x101,0x102,0x103的内存处。
- 大端法(最高有效字节存在最前面,即低地址处)
0x100 | 0x101 | 0x102 | 0x103 | ||
---|---|---|---|---|---|
··· | 01 | 23 | 45 | 67 | ··· |
- 小端法(最低有效字节存储在最前面)
0x100 | 0x101 | 0x102 | 0x103 | ||
---|---|---|---|---|---|
··· | 67 | 45 | 23 | 01 | ··· |
注:大多数Intel兼容机采用小段模式,IBM和 Sun公司机器大多采用大端法,ARM架构的处理器支持双端法,但在Android和iOS系统之恶能运行小端模式。
6 十六进制与二进制对应关系
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
十六进制 | 8 | 9 | A | B | C | D | E | F |
二进制 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
例如:字符串"abced",其在内存中存储信息为61 62 63 64 65 00,(C语言中字符串最后一位为null,即00)这个存储信息就是十六进制的,即十六进制的ASCII编码。单独拿出"a"对应的61看,十六进制数字61转为二进制可通过上表查到,6对应0110,1对应0001,即61对应二进制为01100001。
7 布尔运算
-
非(NOT, ∼ \sim ∼): ∼ 0 = 1 \sim0=1 ∼0=1 ∼ 1 = 0 \sim1=0 ∼1=0
-
与(AND,&): 0 & 0 = 0 0 \& 0=0 0&0=0 0 & 1 = 0 0 \& 1=0 0&1=0 1 & 0 = 0 1 \& 0=0 1&0=0 1 & 1 = 0 1 \& 1=0 1&1=0
-
或(OR,|): 0 ∣ 0 = 0 0|0=0 0∣0=0 0 ∣ 1 = 1 0|1=1 0∣1=1 1 ∣ 0 = 1 1|0=1 1∣0=1 1 ∣ 1 = 1 1|1=1 1∣1=1
-
异或(EXCLUSIVE-OR, ∧ \wedge ∧): 0 ∧ 0 = 0 0\wedge0=0 0∧0=0 0 ∧ 1 0\wedge1 0∧1=1 1 ∧ 0 = 1 1\wedge0=1 1∧0=1 1 ∧ 1 = 0 1\wedge1=0 1∧1=0
8 C语言中的位级运算
C语言一个特性就是支持按位进行布尔运算
例如: ∼ \sim ∼ 0x41,二进制为 ∼ \sim ∼[0100 0001],运算结果为[1011 1110] ,运算结果十六进制表示为0xBE
位运算的一个常见用法就是实现掩码运算,例如:对于操作数0x89ABCDEF,如果想得到该操作数最低有效字节的值,可以通过&0xFF,就可以得到最低有效字节0x0000 00EF。
- 左移运算
左移几位就丢弃最高的几位,并在右端补相应个数的0 - 右移运算
(1) 逻辑右移:右移几位就丢弃最低的几位,并在左端补几个0
(2) 算术右移:右移几位就丢弃最低的几位,左端补1
unsigned右移是逻辑右移,signed右移一定是算术右移
9 整数的表示
- 带有正、负号的二进制数称为真值。在计算机中约定:在有符号数的前面增加一位符号位,0表示正号,1表示负号。这种数被称为机器数。机器数的编码方式有原码、反码、补码3种。
- 原码:正数的符号位用0表示,负数的符号位用1表示,其余各位数表示数值本身
例如:+1010110表示为01010110;-0110101表示为10110101 - 反码:正数的反码与其原码相同;负数的反码为保持其原码符号位不变,其余各位按位取反得到
例如:+1010110表示为01010110;-0110101表示为11001010 - 补码:正数的补码与原码相同;负数的补码为保持原码符号位不变,其他位取反,最后再加1运算,即用其反码加1。
例如:+1010110表示为01010110;-0110101表示为11001010+1即11001011
9.1 补码的意义
两种方式理解:
- 例如十进制数-5,二进制原码为1101,要得到-5的补码就需要找到与+5和为10000的二进制数(为什么这么找呢,我理解是-5补码地址位的1有了数值含义,即表示 − 2 4 -2^4 −24,因此需要后面4位加 − 2 4 -2^4 −24等于-5,反过来说就是后面4位与+5的和等于 2 4 2^4 24(即10000)),+5原码为0101,0101与1010和为1111,1111再加1就等于了10000,所以找到的那个二进制数为1010+1,即得到-5补码为1011。
- -5的补码为1011,由于最高位有了数值意义,其表示-8,另外两位上的1分别表示+2,+1,这样就可以根据补码直接计算出补码对应的十进制数。
9.2 byte的范围
1byte=8bits,即8位。对一个8位数据来说,其补码最小数字为[1000 0000]即 − 2 8 − 1 -2^{8-1} −28−1= -128,最大数字为[0111 1111]即 2 8 − 1 − 1 2^{8-1}-1 28−1−1=127,所以1byte的范围为 − 128 ∼ 127 -128\sim127 −128∼127
9.3 有符号数和无符号数的转换
short int a =-12345;
unsigned short b = (unsigned short)a;
printf(”a= %d , b = %u” , a, b);
在C语言中将一个有符号数-12345(十进制)强转为无符号数,转化结果为53191(十进制)。
但两个数的二进制表示是一样的,都是1100 1111 1100 0111。
-
有符号(T)转无符号(U)满足下面公式:
T 2 U w ( x ) = { x + 2 w x < 0 x x ≥ 0 T2U_w(x)=\begin{cases} x+2^w & x<0 \\ x & x\geq0 \\ \end{cases} T2Uw(x)={x+2wxx<0x≥0x x x:有符号数( x ≥ 0 x\geq0 x≥0即最高位数字是0, x < 0 x<0 x<0即最高位数字是1)
T 2 U w ( x ) T2U_w(x) T2Uw(x):转换后的无符号数
w w w:二进制数的位数
-
无符号转有符号满足下面公式:
U 2 T w ( u ) = { u u ≤ T M a x w u − 2 w u > T M a x w U2T_w(u)=\begin{cases} u & u\leq TMax_w \\ u-2^w & u>TMax_w \\ \end{cases} U2Tw(u)={uu−2wu≤TMaxwu>TMaxw
u u u:无符号数( u ≤ T M a x w u\leq TMax_w u≤TMaxw即最高位数字0, u > T M a x w u>TMax_w u>TMaxw即最高位数字是1)
U 2 T w ( u ) U2T_w(u) U2Tw(u):转换后的有符号数
w w w:二进制数的位数
9.3.1 转换案例
在C语言中,有符号数和无符号数进行运算时,C语言会隐式的将有符号数强制转换成无符号数来执行运算。
1 int i = -1;
2 unsigned int b = 0;
3 if(a < b)
4 printf(”−1 < 0”)
5 else
6 printf(”−1 > 0”)
执行结果为-1>0。
这是因为-1(二进制表示为32个1)被转换为无符号数,其十进制的值就变成了 2 32 − 1 2^{32}-1 232−1。
9.4 扩展与截断
9.4.1 扩展
- 无符号数转成更大的数据类型:只需在扩展的数位进行补0即可,称为零扩展
- 有符号数转更大的数据类型:
- 非负有符号数:扩展的数位补0即可
- 负数有符号数:扩展的数位需要补1
转换定理:当有符号数从一个较小的数据类型转换成较大类型时,进行符号位扩展,可以保持 数值不变。
9.4.2 截断
- 无符号数: 将一个 w 位的无符号数,截断成 k 位时,丢弃最高的 w-k 位,截断操作可以对应 于取模运算,即除以 2 的 k 次方之后得到的余数。
- 有符号数:
- 我们用无符号数的函数映射来解释底层的二进制位,这样一来我们就可以使用与 无符号数相同的截断方式,得到最低 K 位;
- 我们将第一步得到的无符号数转换成有符号数。
10 整数的运算
10.1 无符号数加法溢出
c = a + b − 2 w c=a+b-2^w c=a+b−2w ,其中 w w w为二进制数的位数;
例如:0(0000 0000) = 255(1111 1111) + 1(0000 0001) - 2 8 2^8 28
C语言中判断是否溢出代码如下:
1 int uadd_ok(unsigned x, unsigned y){
2 unsigned sum = x + y;
3 return sum >= x; // 溢 出 返 回 0, 没 溢 出 返 回 1
4 }
10.2 补码加法溢出
- 当 x 加 y 的和大于等于 2 w − 1 2^{w−1} 2w−1 时,发生正溢出,此时,得到的结果会减去 2 w 2^w 2w
- 当 x 加 y 的和小于 − 2 w − 1 −2^{w−1} −2w−1 时,发生负溢出,此时,得到的结果会加上 2 w 2^w 2w
10.3 加法逆元(相反数)
对于减法运算可以转化为加法逆元,即 y − x → y + x ′ y-x\rightarrow y+x\prime y−x→y+x′ ( x ′ 为 x 的 加 法 逆 元 , 也 称 相 反 数 x\prime为x的加法逆元,也称相反数 x′为x的加法逆元,也称相反数)
w 位 的 无 符 号 数 逆 元 x ′ = { x x = 0 2 w − x x ≥ 0 w位的无符号数逆元x\prime=\begin{cases} x & x=0 \\ 2^w-x & x\geq0 \\ \end{cases} w位的无符号数逆元x′={x2w−xx=0x≥0
w 位 的 有 符 号 数 逆 元 x ′ = { − x x > T M i n w T M i n w x = T M i n w w位的有符号数逆元x\prime=\begin{cases} -x & x>TMin_w \\ TMin_w & x=TMin_w \\ \end{cases} w位的有符号数逆元x′={−xTMinwx>TMinwx=TMinw
T
m
i
n
w
Tmin_w
Tminw:w位有符号数的最小值
T
M
i
n
w
+
T
M
i
n
w
=
−
2
w
−
1
+
(
−
2
w
−
1
)
=
−
2
w
TMin_w+TMin_w=-2^{w-1}+(-2^{w-1})=-2^w
TMinw+TMinw=−2w−1+(−2w−1)=−2w
因 为 − 2 w < − 2 w − 1 , 发 生 负 溢 出 , 所 以 得 到 结 果 会 加 上 2 w , 如 下 式 : 因为-2^w<-2^{w-1},发生负溢出,所以得到结果会加上2^w,如下式: 因为−2w<−2w−1,发生负溢出,所以得到结果会加上2w,如下式:
T M i n w + T M i n w 的 逆 元 = − 2 w + 2 w = 0 因 此 T M i n w 逆 元 是 其 本 身 TMin_w+TMin_w的逆元=-2^w+2^w=0 \\ 因此TMin_w逆元是其本身 TMinw+TMinw的逆元=−2w+2w=0因此TMinw逆元是其本身
10.4 乘法运算
-
无符号数乘法
w 位的无符号数 x 和 y,二者的乘积可能需要 2w 位来表示。在 C 语言中,定义了无符号数乘法所产生的结果是 w 位,因此,运行结果会截取 2w 位中的低 w 位。截断采用取模的方式,因此,运行结果等于 x 与 y 乘积并对 2 的 w 次方取模
-
补码乘法
计算机的有符号数用补码表示,因此补码乘法就是有符号数乘法。无论是无符号数 乘法,还是补码乘法,运算结果的位级表示都是一样的,只不过补码乘法比无符号 数乘法多一步,需要将无符号数转换成补码(有符号数)。
虽然完整的乘积结果的位级表示可能会不同,但是截断后的位级表示都是相同的
注:如果乘以的是2的整倍数,可以通过左移进行快速计算。
例如:chart 9(0000 1001),左移一位变为18(00010010),等于 9 × 2 9\times2 9×2
又例如: x × 14 = x ⋅ ( 2 4 − 2 1 ) = ( x < < 4 ) − ( x < < 1 ) x\times14=x\cdot(2^4-2^1)=(x<<4)-(x<<1) x×14=x⋅(24−21)=(x<<4)−(x<<1)
10.5 除以2的倍数也可以用移位运算实现
-
无符号数采用逻辑右移
-
有符号数(补码)采用算术右移
- 当补码小于0,需要先加上偏置(偏执量为 2 k − 1 2^k-1 2k−1)再进行算术右移
- 当补码大于0,可以直接进行算术右移
除法结果总是向0舍入,如 3.14 → 3 3.14\rightarrow3 3.14→3, 3.14 → 3 3.14\rightarrow3 3.14→3
11 浮点数
11.1 IEEE浮点数表示
V = ( − 1 ) s × M × 2 E V=(-1)^s\times M\times2^E V=(−1)s×M×2E
s s s:符号位; E E E:阶码; M M M:尾数
例如:C语言中float为4个byte,占32bits,这32个bits划分如下:
最高位31为符号位(s=0为正数,s=1为负数)。23-30位这八位与阶码E的值有关,剩余23位与尾数M相关。
IEEE浮点表示可以分为一下三种
- 当阶码字段不全为0,且不全为1,此时表示规格化的值
- 当阶码字段全为0,此时表示非规格化的值
- 当阶码字段全为1,表示特殊值(无穷或“不是一个数,NaN”)
11.2 整形转单精度浮点型
例如整形12345,对应二进制为[0000 0000 0000 0000 0011 0000 0011 1001],前18位没有意义,只看后14位,即11 0000 0011 1001。其中第一位为符号位,看后13位,由于单精度的小数字段长度位23,需要在后面补10个0,即[1 0000 0011 1001 0000 0000 00],这个二进制数即为单精度浮点数的小数字段。
从12345 的规格化表示可以发现阶码 E 的值等于 13,由于单精度浮点数的 bias 等于 127,因此根据公式 E=e-bias,可以计算出 e 的值等于 140,其二进制为1000 1100,这个二进制数即为浮点数的阶码字段。
最后再加上符号位的0,就可以推出来整个单精度浮点数的二进制数字。
11.3 舍入
- 向偶舍入
- 向零舍入
- 向下舍入
- 向上舍入