文章目录
信息的表示和处理
01 信息的存储
虚拟地址空间
数据的最小单位是bit,一个比特表示一个1或0,8个比特组成一个Byte
- 二进制单字节范围:00000000~11111111
- 十进制单字节范围:0~255
一般来说,程序会将内存视为一个非常大的数组,数组又由一个个的字节组成(图中的一个方框就是一个字节),每个字节通过唯一的一串数字来表示,一般称为地址,所有地址的几何就是虚拟地址空间
虚拟空间的最大空间范围是由机器字长决定的,对于字长为w位的机器,范围为$2^w-1 $
- 32位计算机地址空间 0 ∼ 2 32 − 1 0\sim2^{32}-1 0∼232−1,4GB
- 64位计算机地址空间 0 ∼ 2 64 − 1 0\sim2^{64}-1 0∼264−1,16EB
- 32位机器在嵌入式场景中仍有应用,64位机器是日常设备主流,且基本能向后兼容
- C语言的long、指针类型在两种机器上所占字节数大小有差别,其他基本一致
- 在64机器上可以自定义命令来生成可以在32位机器上运行的程序
linux> gcc -m32 -o hello32 hello.c
linux> gcc -m64 -0 hello64 hello.c
数据在虚拟地址空间中的存储有大端法和小端法两种
- 大端法:数据低位存储在地址高位(大多数IBM、Sun)
- 小端法:数据低位存储在地址低位(大多数Intel兼容机、Android、iOS)
- 也有支持双端法的处理器,例如基于ARM架构的处理器
C语言的变量类型
- int和float同样表示一个数,字节模式(该数存储成16进制的值)是完全不同的
- 字符串为以NULL结尾的字符数组,故abcde字符串的长度为6
02 整数表示与编码
有符号的二进制数的表示
十进制数有正负之分,二进制数也有,带正负的二进制数称为真值
在计算机中通常在有符号数的前面增加1位符号位,用0表示正号,用1表示符号
- 这种用0和1表示正负号的数称为机器数
- 目前常用的机器数编码方式有源码、反码和补码
原码、反码、补码
原码
正数符号位用0,负数符号位用1,其余数位表示数值本身
例如,+1010110
的原码为01010110
,-0110101
的原码为10110101
原码表示很简单,但是用原码表示的数进行加减法的运算很麻烦,例如异号相减
反码
正数的反码与原码相同,负数的反码是在原码的基础上保持符号位不变,其余各位按位求反
例如,+1010110
的反码为01010110
,-0110101
的反码为11001010
补码
正数的补码与原码相同,负数的补码是在原码的基础上保持符号位不变,其余各位按位求反加一,即负数的补码是其反码加1
例如,+1010110
的补码为01010110
,-0110101
的补码为11001011
计算机中有符号整数常用补码形式存储,且任意一个数补码的补码是原码
为什么补码的计算规则是这样的,有什么意义?
-5
,补码为1011
,从二进制补码能快速得出对应十进制为-8+2+1=-5
- 对于4bit数据,补码能表示的最小数字为
1000
,即-8
- 对于4bit数据,补码能表示的最大数字为
0111
,即7
- 对于8bit数据,补码能表示的最小数字为
1000 0000
,即-128
- 对于8bit数据,补码能表示的最大数字为
0111 1111
,即127
- 对于4bit数据,补码能表示的最小数字为
- 当两个数做减法时,可以用加上一个数的补码代替减去那个数
有符号数和无符号数转换
C语言允许数据之间做强制类型转换,例如
short int a =-12345;
unsigned short b = (unsigned short)a;
printf(”a= %d , b = %u” , a, b);
-12345经过强制类型转换的得到无符号整数53191
1100 1111 1100 0111
对应-123451100 1111 1100 0111
对应53191
有符号转无符号
- 如果是正数,就相同
- 如果是负数,就是原来的值加上$2^w $,其中w为类型长度(包含符号位)
无符号转有符号
- 如果是正数,就相同
- 如果是负数,就是原来的值减去$2^w $,其中w为类型长度(包含符号位)
为什么要了解这个转换:C语言隐式转换影响运算结果
int i = -1;
unsigned int b = 0;
if(a < b)
printf(”−1 < 0”)
else
printf(”−1 > 0”)
由于b是无符号整数,实际和0比较的不是-1,而是转换后的4294967295( − 1 + 2 32 -1+2^{32} −1+232)
扩展一个数字的位表示
从unsigned char(8)转变为unsigned short(16)类型,只需要在扩展数位进行补0即可
当有符号数表示非负数时,最高位是 0,此时扩展的数位进行补零即可;当有符号数表示负数时,最高位是 1,此时扩展的数位需要进行补 1
当有符号数从一个较小数据类型转换成较大类型时,进行符号位扩展,可以保持数值不变
截断一个数字的位表示
将int类型强制转换成short类型时,int类的最高位16位数据被丢弃,留下低16位数据
- 无符号数
- w位的无符号整数,截断成k位。可用于取模运算,即除以2的k次方之后得到的余数
- 有符号数
- 用无符号数的函数映射,使用与无符号数相同的截断方式,得到最低K位
- 将上一步得到的无符号数转成有符号数
03 整数运算
无符号数加法溢出
加法本身很简单,但是加法可能会导致溢出
unsigned char a = 255;
unsigned char b = 1;
unsigned char c = a + b;
printf(” c=%d”, c);
两者相加的结果c会溢出,实际结果不是256而是0
对于溢出是不会报错的,需要手动添加运算结果判定
有符号数加法溢出
计算机的有符号数用补码表示,因此补码加法就是有符号加法
char x = 127;
char y = 1;
char z = x + y;
printf(” z=%d”, z);
与无符号数不同,有符号数的溢出有正溢出和负溢出
- 正溢出会在原有结果上减去2的w-1次方,w为类型长度
- 负溢出会在原有结果上加上2的w-1次方,w为类型长度
- 该代码的运行结果为-128,发生了正溢出
无符号数乘法
w位的无符号数x和y,乘积的结果依然是w位,相当于x与y的乘积结果对2的w次方取模(除以2的w次方的余数)
有符号数乘法
结果同无符号数乘法一致,也是w位,但多了一步将无符号数转化为有符号数
位移快速运算(乘除2的倍数)
原码运算
对原码进行移动,移动单位等于2的倍数,乘左移补0,除右移补0
补码运算
正数同原码一致
负数,保持符号位不变,其余位数乘法左移补0,除法右移补1
04 浮点数
IEEE浮点表示
V = ( − 1 ) s × M × 2 E V=(-1)^{s} \times M \times 2^{E} V=(−1)s×M×2E
三个变量:符号s、阶码E、尾数M
浮点数的数值通过阶码划分为3类
- 规格化:阶码不全为0且不全为1(exp)
- 非规格化:阶码全为0
- 特殊值:阶码全为1,特殊值也分两类,一类表示无穷大或无穷小,一类表示不是【
{待完善:三种类型的具体表示方法}
05 程序编码
演示程序
#include <stdio.h>
void mulstore(long ,long ,long *);
int main() {
long d;multstore(2,3,&d) ;
printf(” 2∗ 3 −−>%1d \n”,d) ;return 0;
}
long mult2(long a,long b){
png s = a* b;return s;
}
两个源文件可以通过Linux> gcc -Og -o prog main.c mstore.c
生成可执行文件
编译选项-Og
是用来告诉编译器生成符合原始C代码整体结构的机器代码
- 为了获得更高的性能,会使用
-O1
或者-O2
,甚至更高的编译优化选项 - 但高级别的优化产生的代码会严重变形,和源代码的关系难以理解
生成汇编文件
可以通过linux> gcc -Og -S mstore.c
生成汇编文件mstore.s
两种保存器
调用者保存寄存器,被调用者保存寄存器