2011年4月25日星期一

移位和尾端

遭遇多次了,还是记下来好。曾经看到一些对尾端的描述,用的是“前前后后”、“左左右右”这些个相对参照,描述的内容一样,每各人每次的理解常常不一致。所以先定义一些“绝对参照”:

对于内存中数据的表示,屏幕上,纸上,从左到右,从上到下,地址从小到大。
对于逻辑上数据的表示,屏幕上,纸上,从左到右,从上到下,先是高位(MSB),再是低位(LSB)。

1. 移位操作是有独立意义逻辑意义的,不依赖尾端。以0x0102为例:
    <<. 左移4位,就增大16倍,变成0x1020
    >>. 右移4位,就用整数除法减小了16倍,变成0x0010

2. 尾端影响的是数据在内存、CPU、线路上存储,传输和处理时的排布。以0x0102为例,在大尾端机器内存中的顺序是01 02,在小尾端机器内存中的顺序是02 01。识记方法,我们用的x86电脑是小尾端的,小尾端,低地址对应低位,高地址对应高位。

3. 字节为单位,8比特
以IPv4和IPv6中的头部字段中的片偏移和标志位为例,演练一下移位操作和尾端。frag_off保存的是一个16位的大尾端数据,其中有13位是片偏移,3位是标志位,排布如下:
IPv4中:(0(1),DF(1),MF(1),Fragment-Offset(13))
IPv6中:(Fragment-Offset(13),Res(2),M(1))
其中,Res是保留位,全部置0,MF与M是等价的两个标志

从IPv4的格式转换为IPv6的格式
#define IP_OFFSET 0x1fff
#define IP_MF     0x2000

frag_off_6 = (ntohs(frag_off_4) & IP_OFFSET) << 3;
frag_off_6 &= 0xfff8;
frag_off_6 |= (ntohs(frag_off_4) & IP_MF) ? 0x0001 : 0x0000;
frag_off_6 = htons(frag_off_6);
总结:不纠结于那8+5=13的比特边界,先把数据转成宿主机的格式(ntohs),然后换照“绝对参照”中的逻辑,该怎么操作就怎么操作,操作完成后,再转成目标尾端(htons)。

4. Bit Field,下面这个结构定义是怎么回事?
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
        version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
          ihl:4;
#else
#error    "Please fix <asm/byteorder.h>"
#endif
计算机在读写内存的时候,逻辑上最小的单位是1个字节,也就是8比特。IPv4头部中version和ihl各占4比特,编译器要把它们组装成1个字节。一般来说,传输时,version在前,ihl在后。因此,这2个4比特的组装顺序不是儿戏。

文章最开始规定的“绝对坐标”也是编译器和代码编写者之间的逻辑约定,地址前低后高。以小尾端为例,按第2条描述的识记方法,低(ihl)对应LSB,高(version)对应MSB,因此组装后的字节就是(version,ihl),Bingo!

如果代码就按defined(__LITTLE_ENDIAN_BITFIELD)来写,在大尾端机器中会怎么样呢?低(ihl)对应MSB,高(veresion)对应LSB,因此组装后的字节就是(ihl,version),反啦!

5. 有用的链接
[1]. Low Level Operators and Bit Fields, http://www.cs.cf.ac.uk/Dave/C/node13.html

没有评论:

发表评论