今天在主页上看到一句话“程序员之所以犯错误,不是因为他们不懂,而是因为他们自以为什么都懂。”挺打动我的,很多错误是我们自以为的认为程序怎样怎样的跑,然后就跑飞了。
先来看个问题:
小明1岁就会说话了,喊了声”妈妈“,小明他妈就死了,后来,喊爷爷时,爷爷死了。但是他喊爸爸时,隔壁老王死了。—网络
这里程序猿A说,有bug啊,喊爸爸时候,隔壁老王死了,那之前喊爷爷时候,怎么可能是小明爷爷死呢,应该是老王他爹死才对。这时,只见程序猿B摇摇头的叹气:冤冤相报何时了。0_0!
一:左移
左移: 将a的二进制数左移2位,右补0,位左移后溢出,舍弃。
百度上看到这样一句话,这句话很多书页有,“左移一位相当于该数乘以2,但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。”这句话不能说它全错,但又不恰当。根据这句话意思,如一个char a=128(10000000);如左移1位,a=0。
实际上,如果“<<”的左操作数为字符类型、short 或unsigned short 类型时,左操作数存在隐式的类型转换,即整数提升(Integer promotion),此时左操作数被转换为int 类型参与运算(其中有一个例外,就是如果short 类型与int 类型相同,unsigned short会被转换为unsigned int)。这样从本质上来说,参与运算的并非是a 本身,而是(int)a或(unsigned int)a 的值,运算所求得的值也并非是a 的类型,而是int 类型或unsigned int类型。”-----C程序中的谬误
char a = 128;
printf("a<<1:%d\n",a<<1);
这个结果输出的就是256了。当然,如果如下这样写:
char a = 128;
a = a << 1;
printf("a=%d\n", a);
这时涉及到的就是数据截断了,相当于如下代码:
char a = 128;
int b = 0;
b = a << 1;
a = (char)b;
printf("a=%d\n", a);
截取的时候是截取低位(),
二:右移
右移:按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1。----百度
简单理解就是原来符号位是啥,右移时就补啥。很多地方能看到这句话“右移一位相当于除以2,右移n 位相当于除以2n。 ”对于正数还比较好理解,二进制每高一位都是原来的2倍,而右移一位高位补0,即每一位都除于2。当负数,右移高位补的是1,为啥右移还是除于2呢。这涉及到数值在计算机的存储方式,以及这种存储方式对数值运算的作用。下次再讲_
三:大小端
大端(网络序):最高字节存放在内存的最低位字节地址上,而字数据的低字节则存放在高地址中;小端则相反;
假设一个4字节的字:0x01020304;存储地址从00-03
内存地址:0x00000000 0x00000001 0x00000002 0x00000003
大端存储 0x01 0x02 0x03 0x04
小端存储 0x04 0x03 0x02 0x01
-----回到左、右移话题_
假设一个int a =128;(0x00 0x00 0x00 0x80)那么它在不同大小端系统中存储如下
内存地址:0x00000000 0x00000001 0x00000002 0x00000003
大端存储 00000000 00000000 000000000 10000000
小端存储 10000000 00000000 000000000 00000000
这样一个值左移一位,即a<<1。根据上图得到如下结果:
内存地址:0x00000000 0x00000001 0x00000002 0x00000003
大端存储 00000000 00000000 000000001 00000000
小端存储 00000000 00000000 000000000 00000000
显然大端得到的结果就是0x00 00 01 00,即十进制的256。而小端设备得到的确实0。奇怪了吧,难道左右移的算法(乘2、除2)还要根据大小端不成?Duang~~关键点来了:
(1) 数据在寄存器中都是以大端模式次序存放的。
(2) 对于内存中以小端模式存放的数据。CPU存取数成时,小端和大端之间的转换是通过硬件实现的,没有数据加载/存储的开销。—忘记哪里看到的了
大小端是针对数值在内存中的存储方式,如我们启动一个进程,数据都是在内存中的;而当数据要参与运算时,即执行到该机器指令时,数据被cpu调到寄存器中运算,而寄存器都是以大端模式存放,再参与运算,然后将结果根据大小端规则重新放回内存中。所以,不管大小端设备,128<<1都是256。
四:hton还是ntoh
前面的左右移和大小端都是为本节的铺垫
到底是要用hton,或是ntoh,第一次看到这两个函数,觉得蛮简单的,无非是收到将一个数据转为主机序或网络序(大端),看完百度百科后发现,偶滴神啊,还真是这样~~!值到后面碰到一个bug。。。
一次验证中发现,原来这两个函数就是一个篮球、一个足球—壳不一样,里面全是空气。。。也就是两个函数的实现一模一样
#define ___swab32(x) \
({ \
__u32 __x = (x); \
((__u32)( \
(((__u32)(__x) & (__u32)0x000000ffUL) << 24) | \
(((__u32)(__x) & (__u32)0x0000ff00UL) << 8) | \
(((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) | \
(((__u32)(__x) & (__u32)0xff000000UL) >> 24) )); \
})
uint32_t htonl(uint32_t hostlong)
{
return (checkCPUendian() ? (hostlong) : ___swab32(hostlong))
}
uint32_t ntohl(uint32_t hostlong)
{
return (checkCPUendian() ? (hostlong) : ___swab32(hostlong))
}
那么,问题来了。为啥一样的代码能实现不同功能。其实,这里我们理解的”功能“只是从hotn、ntoh的表面意义去理解,实际上这两个函数的操作无非时将数值转换一遍进行存储,而怎么转换取决于主机序。如果,主机序等于网络序–即都是大端,那不管是hton还是ntoh都是啥也不用干,直接赋值即可;相反,主机序不等于网络序,不管是hton还是ntoh都是把数值反转一遍,因为反转一遍即为网络序,再反转一遍又变回主机序。
所以,如果一个设备的主机序是小端的,那么不管调用hton还是调用ntoh都能把一个值转为网络序。其实,现在行业已经规定设备间通信都用网络序通信,因此,你在发送数据前需要转为网络序,即调用hton,但你使用ntoh也完全没问题;同样,从网络收到一个数据,需要转为主机序,你调用hton也一样能取到正确的数值(只要发送端转过一次)。
网络通信无非就是把一堆数值从一个设备送到另一个设备,不管另一个设备在何方、是何物。只要能128送过去还是128,而不会变成821就可以。现在分析一个数值的发送过程:A(小端)发送0x01020304===》16909060到B(大小端未知);
int b = 16909060;/* a是一个整型 */
b的存储方式如下:
内存地址:0x00000000 0x00000001 0x00000002 0x00000003
小端存储 04 03 02 01
如果不进行转网络序,msg.a = b;
则A申请一个内存msg放这个值,msg存储同上;那么B收到数据,也是这样的顺序。
如果B是大端设备,那得到的值就是0x04030201 =》67305985,显然错了;如果是小端,b = ntoh(msg.a);msg.a = 0x01020304=>hton后也变成了0x04030201,同样错了。
因此A中发送之前必须转为网络序msg.a = hton(b),此时a的值就已经不等于b了。
五:值通信
项目中经常看到消息发送前这样封装的
#define ENCODE_INT32(BUF, INDEX, DATA32) do { \
*((u_int8_t *)(BUF) + (INDEX)) = (u_int8_t)((DATA32) >> 24); \
(INDEX)++; \
*((u_int8_t *)(BUF) + (INDEX)) = (u_int8_t)((DATA32) >> 16); \
(INDEX)++; \
*((u_int8_t *)(BUF) + (INDEX)) = (u_int8_t)((DATA32) >> 8); \
(INDEX)++; \
*((u_int8_t *)(BUF) + (INDEX)) = (u_int8_t)(DATA32); \
(INDEX)++; \
} while (0)
使用时,对于要发送的4字节的数值通过这个方法封装,其实就是值通信的概念;即DATA32值是多少,就发送多少,不关心大小端。有点像hton的方法,我们知道hton是针对字节数大于1的数值,对于1个字节没必要转,而上述这种封装方法即是把4个字节按低位到高位顺序拆开成4个字节存储。所以接收方收到时也必须按低位到高位顺序方式再组合成一个4字节数值,即能达到hton、ntoh效果;DECODE如下:
#define DECODE_INT32(BUF, INDEX, RESULT32) do {\
(RESULT32) = *((u_int8_t *)(BUF) + (INDEX)) << 24; \
(INDEX)++; \
(RESULT32) |= *((u_int8_t *)(BUF) + (INDEX)) << 16; \
(INDEX)++; \
(RESULT32) |= *((u_int8_t *)(BUF) + (INDEX)) << 8; \
(INDEX)++; \
(RESULT32) |= *((u_int8_t *)(BUF) + (INDEX)); \
(INDEX)++; \
} while (0)