一般的,计算机使用8位的块,即字节(byte)为最小单元寻址
每台计算机都有一个字长(word size), 虚拟地址以这样的一个字来编码,即对于一个字长为w的机器而言,虚拟地址的范围为0~2^w-1
例如32位的计算机,虚拟地址空间为0~2^32-1 ≈ 4GB
C声明 32位机器 64位机器
char 1 1
short 2 2
int 4 4
long 4 4
long long 8 8
float 4 4
double 8 8
char* 4 8 //note 在32位与64位机器上可能会出错
大端与小端(数值型)
小端法(little endian):最低有效字节在最前面 (Intel兼容系列)
大端法(bi-endian) : 最高有效字节在最前面 (IBM与Sun的大型机系列)
例如 x=0x01 23 45 67,且存储在0x100上,那么:
0x100 0x101 0x102 0x103
大端法: 01 23 45 67
小端法: 67 45 23 01
可能使用的地方:
1 小端机器与大端机器的网络通信
2 检查机器程序进行反汇编时
3 通过强制转换打印数据的位表示
ex:
typedef unsigned char* byte_pointer;
void show_bytes(byte_pointer start, int len){
int i = 0;
for(i = 0; i < len; i++){
printf("%.2x", start[i]);
}
printf("\n");
}
//自然的,字符串并不会有大端,小端问题
//note 如何在C\C++中使用utf-8??
~0 产生一个全1的补码,且与机器字长无关
&并 |与 ~反 ^异或(取补)
<< 左移,低位补0
>> 逻辑右移 高位补0
>> 算术右移 高位最高有效位(大部分编译器是这种移位) (无符号数则是逻辑右移,是的) (java中 >> 表示算术右移 >>> 表示逻辑右移)
对于移动超过数据本身的移位,只会移动 k mod w 位,如int 32位,若
int lvar = 0xFEDCBA98 << 32; // 32 mod 32 = 0
int lvar = 0xFEDCBA98 >> 36; // 36 mod 32 = 4
补码:最高位表示负权 -2^(w-1)
反吗:最高位表示负权 -(2^(w-1) - 1) //有两种0 [00..0] [11..1]
原码: 最高位是符号位, 用来确定剩下的位去负权还是正权 //有两种0 [00..0] [10..0]
具体见:http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
符号数在和无符号数比较时,有符号数会先转换成无符号数,所有 -1<0U = false;
INT_MIN 为何写成-2147483647-1?因为C++对负数的操作都是·用正数取负来完成的,那么:
-2147483648 就是对2147483648取负操作的表达式。而 2147483648 无法用一个 int 型来表示,所以用-2147483647-1来表示
无符号数扩展,开头添0(零扩展);补码扩展,添最高有效位(符号扩展),所以一个负数的补码在不同的字长的机器上表现是不同的
将short转换为unsigned int时,首先改变大小,之后完成有符号到无符号的转换,即
(unsigned)sx = (unsigned)(int)x 而不是 (unsigned)sx = (int)(unsigned short)sx; 会影响编码
memcpy(void *dest, void *src, size_t n); szie_t strlen(char *s);等许多库函数的参数与返回值用了size_t, size_t在32位机器上被定为为unsigned int
所以一定要注意:1.有无符号在相互赋值时的隐性转换 2.在比较时,有符号数隐性转换为无符号数
乘以常数时,通常用左移来实现,例如,x*14,则,14 = 2^3 + 2^2 + 2^1; x*14 --编译器--> (x << 3) + (x << 2) + (x << 1)
除以常数时,通常用右移来实现,无符号使用逻辑移位,补码使用算术移位。
在补码数为负,即最高位为1时,右移时会向上去整,所以有,使用算术移位的补码除法: (x <0 ? (x + (1 <<k) - 1) : x) >> k
//一些函数
//检测无符号加法溢出
int udd_ok(unsigned x, unsigned y){
unsigned sum = x + y;
return sum >= x;
}
//检测补码加法溢出
int tadd_ok(int x, int y){
int sum = x + y;
int neg_over = x < 0 && y < 0 && sum >= 0; //负溢出
int pos_over = x >=0 && y >=0 && sum < 0; //正溢出
return !neg_over && !pos_over;
}
//检测补码减法溢出
int tsub_ok(int x, int y){
if(y == INT_MIN && x > 0){
return 1;
}else{
return tadd_ok(x, -y); //NOTE:y=INT_MIN时,也有-y=INT_MIN
}
}
//检测补码乘法溢出
int tmult_ok(int x, int y){
int p = x*y;
return !x || p/x == y;
}
int tmult_ok(int x, int y){
long long pll = (long long)x*y; //(long long)的强制转换要在乘法这进行
return pll == (int)pll;
}
浮点数
二进制小数点的权
... 8 4 2 1 . 1/2 1/4 1/8 1/16...
....1 0 0 1 . 1 0 1 0 ... = (8+1).(0.5 + 0.125) = 9.625
IEEE浮点数的表示
(-1)^s * M *2^E来表示一个数
s 符号:决定这个数是 正数(0) 负数(1)
M 尾数:二进制小数 (n位表示)
E 阶码:对浮点数加权 (k位表示)
IEEEE标准中
32位的float s = 1;k = 8;n=23
64位的float s = 1; k = 11;n=52
每台计算机都有一个字长(word size), 虚拟地址以这样的一个字来编码,即对于一个字长为w的机器而言,虚拟地址的范围为0~2^w-1
例如32位的计算机,虚拟地址空间为0~2^32-1 ≈ 4GB
C声明 32位机器 64位机器
char 1 1
short 2 2
int 4 4
long 4 4
long long 8 8
float 4 4
double 8 8
char* 4 8 //note 在32位与64位机器上可能会出错
大端与小端(数值型)
小端法(little endian):最低有效字节在最前面 (Intel兼容系列)
大端法(bi-endian) : 最高有效字节在最前面 (IBM与Sun的大型机系列)
例如 x=0x01 23 45 67,且存储在0x100上,那么:
0x100 0x101 0x102 0x103
大端法: 01 23 45 67
小端法: 67 45 23 01
可能使用的地方:
1 小端机器与大端机器的网络通信
2 检查机器程序进行反汇编时
3 通过强制转换打印数据的位表示
ex:
typedef unsigned char* byte_pointer;
void show_bytes(byte_pointer start, int len){
int i = 0;
for(i = 0; i < len; i++){
printf("%.2x", start[i]);
}
printf("\n");
}
//自然的,字符串并不会有大端,小端问题
//note 如何在C\C++中使用utf-8??
~0 产生一个全1的补码,且与机器字长无关
&并 |与 ~反 ^异或(取补)
<< 左移,低位补0
>> 逻辑右移 高位补0
>> 算术右移 高位最高有效位(大部分编译器是这种移位) (无符号数则是逻辑右移,是的) (java中 >> 表示算术右移 >>> 表示逻辑右移)
对于移动超过数据本身的移位,只会移动 k mod w 位,如int 32位,若
int lvar = 0xFEDCBA98 << 32; // 32 mod 32 = 0
int lvar = 0xFEDCBA98 >> 36; // 36 mod 32 = 4
补码:最高位表示负权 -2^(w-1)
反吗:最高位表示负权 -(2^(w-1) - 1) //有两种0 [00..0] [11..1]
原码: 最高位是符号位, 用来确定剩下的位去负权还是正权 //有两种0 [00..0] [10..0]
具体见:http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
符号数在和无符号数比较时,有符号数会先转换成无符号数,所有 -1<0U = false;
INT_MIN 为何写成-2147483647-1?因为C++对负数的操作都是·用正数取负来完成的,那么:
-2147483648 就是对2147483648取负操作的表达式。而 2147483648 无法用一个 int 型来表示,所以用-2147483647-1来表示
无符号数扩展,开头添0(零扩展);补码扩展,添最高有效位(符号扩展),所以一个负数的补码在不同的字长的机器上表现是不同的
将short转换为unsigned int时,首先改变大小,之后完成有符号到无符号的转换,即
(unsigned)sx = (unsigned)(int)x 而不是 (unsigned)sx = (int)(unsigned short)sx; 会影响编码
memcpy(void *dest, void *src, size_t n); szie_t strlen(char *s);等许多库函数的参数与返回值用了size_t, size_t在32位机器上被定为为unsigned int
所以一定要注意:1.有无符号在相互赋值时的隐性转换 2.在比较时,有符号数隐性转换为无符号数
乘以常数时,通常用左移来实现,例如,x*14,则,14 = 2^3 + 2^2 + 2^1; x*14 --编译器--> (x << 3) + (x << 2) + (x << 1)
除以常数时,通常用右移来实现,无符号使用逻辑移位,补码使用算术移位。
在补码数为负,即最高位为1时,右移时会向上去整,所以有,使用算术移位的补码除法: (x <0 ? (x + (1 <<k) - 1) : x) >> k
//一些函数
//检测无符号加法溢出
int udd_ok(unsigned x, unsigned y){
unsigned sum = x + y;
return sum >= x;
}
//检测补码加法溢出
int tadd_ok(int x, int y){
int sum = x + y;
int neg_over = x < 0 && y < 0 && sum >= 0; //负溢出
int pos_over = x >=0 && y >=0 && sum < 0; //正溢出
return !neg_over && !pos_over;
}
//检测补码减法溢出
int tsub_ok(int x, int y){
if(y == INT_MIN && x > 0){
return 1;
}else{
return tadd_ok(x, -y); //NOTE:y=INT_MIN时,也有-y=INT_MIN
}
}
//检测补码乘法溢出
int tmult_ok(int x, int y){
int p = x*y;
return !x || p/x == y;
}
int tmult_ok(int x, int y){
long long pll = (long long)x*y; //(long long)的强制转换要在乘法这进行
return pll == (int)pll;
}
浮点数
二进制小数点的权
... 8 4 2 1 . 1/2 1/4 1/8 1/16...
....1 0 0 1 . 1 0 1 0 ... = (8+1).(0.5 + 0.125) = 9.625
IEEE浮点数的表示
(-1)^s * M *2^E来表示一个数
s 符号:决定这个数是 正数(0) 负数(1)
M 尾数:二进制小数 (n位表示)
E 阶码:对浮点数加权 (k位表示)
IEEEE标准中
32位的float s = 1;k = 8;n=23
64位的float s = 1; k = 11;n=52