字节序中的位序
开篇
关于字节序很多书都有讲到,去网上搜字节序或者大端序、小端序,有很多介绍的文章,
但是这些文章基本只讲到大端序或者小端序中字节层面数据是如何存储的,没有讲到
位序是如何的。之前也没想过位序的问题,直到前段时间看到linux kernel中linux/tcp.h的定义:
1 |
|
可以看到头文件使用两个宏(__LITTLE_ENDIAN_BITFIELD和__BIG_ENDIAN_BITFIELD)。
我们知道tcp头的结构如下:
中文版:
对比图片和上面定义的头文件可以看出,针对大端和小端系统,进行了不同的定义,说明在不同字节序中位序也是不同的。
具体可以看这两篇参考:
https://www.fileformat.info/mirror/egff/ch06_04.htm
https://www.linuxjournal.com/article/6788
https://en.wikipedia.org/wiki/Endianness
结论
大端系统中的位序也是"大端",小端系统中的位序也是"小端"
。
即对于0x0a0b0c0d
这个四字节的整数,在内存中是如下存储的:
大端:
1 | +-------------------------------------------------------+ |
小端:
1 | +-------------------------------------------------------+ |
验证大端系统中的位序
我们现在接触到的大部分系统是小端系统,小端系统很好验证,大端系统就比较麻烦,为了验证大端系统,得通过qemu装大端的虚拟机来验证。
参考:
https://stackoverflow.com/questions/2839087/how-to-test-your-code-on-a-machine-with-big-endian-architecture
https://stackoverflow.com/questions/3337896/imitate-emulate-a-big-endian-behavior-in-c
0x00. 安装qemu
去https://www.qemu.org/download/#windows 这个页面下载windows下的qemu,并安装,安装完后记得添加环境变量
0x01. 下载模拟mips的debian镜像
mips芯片是大端的,所以我们模拟这个芯片去验证。
去https://people.debian.org/~aurel32/qemu/mips/下载vmlinux-3.2.0-4-5kc-malta
和debian_wheezy_mips_standard.qcow2
根据页面的说明,打开cmd输入
1 | qemu-system-mips64 -M malta -kernel vmlinux-3.2.0-4-5kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -m 256 |
来启动虚拟机,这里怕内存不够,加了-m 256
的启动参数,测试发现即使使用|-m 512
实际使用的内存也只有256M。
0x02. 启动后发现没法联网
启动后想安装gcc,发现没法联网,这是第一次使用qemu,联网搞了半天。
找到以下解决方法,
在 Windows 上使用 qemu 虚拟机,通过此配置,可以使 qemu 中的虚拟机能连接互联网,并且也可以和 Windows 主机通信。此方式类似于 Vmware 和 VitrualBox 中的桥接网卡。配置方法如下:
- 在 Windows 主机上安装 TAP 网卡驱动: 可下载 openvpn 客户端软件,只安装其中的 TAP 驱动;在网络连接中,会看到一个新的网卡,属性类似于 TAP-Win32 Adapter…,将其名称修改为 tap0。
- 将 tap0 虚拟网卡和 Windows 上连接互联网的真实网卡桥接: 选中这两块网卡,右键,桥接。此时,Windows 主机将不能连接互联网,需要在网桥上配置 IP 地址和域名等信息,才能使 Windows 主机连接互联网。
- qemu 配置: 在虚拟机启动命令行添加以下参数:–net nic -net tap,ifname=tap0;启动虚拟机,并配置虚拟机中的网卡,则虚拟机也可以和 Windows 主机一样,连接互联网和 Windows 主机。
参考:https://my.oschina.net/Czl6BQ6SEmYt/blog/164308
我把TAP网卡重命名为my-tap,所以我的启动参数改为:
1 | qemu-system-mips64 -M malta -kernel vmlinux-3.2.0-4-5kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -m 256 -net nic -net tap,ifname=my-tap |
注意,tap和ifname之间只有一个逗号,不要加空格
,否则报错
0x03. 换软件源
终于能联网了,发现官方源失效了
于是找到阿里云的镜像站中wheezy(Debian7.x)的源替换。
备份官方源
1 | root@debian-mips:~# cp /etc/apt/sources.list /etc/apt/sources.list.backup |
创建新文件
1 | root@debian-mips:~# vi /etc/apt/sources.list |
写入
1 | # deb http://ftp.debian.org/debian wheezy main |
更新apt缓存,安装gcc
1 | root@debian-mips:~# apt-get update |
0x04. 开始验证
测试代码如下:
1 |
|
运行结果
1 | root@debian-mips:~# ./a.out |
因为使用位序的结构体成员不能通过&符号取到地址,所以只能再使用一个struct C来验证结构体内的成员地址是怎么分配的,从c.a,c.b,c.c的地址是递增的,说明在大端系统中,结构体内的成员的地址按照书写顺序递增的。
m->a占3bit,数值是0x5
—–> 101
01010110010101000101110110011
m->b占1bit,数值是0x0
—–> 1010
1010110010101000101110110011
m->c占4bit,数值是0xa
—–> 10101010
110010101000101110110011
m->d占5bit,数值是0x19
—-> 1010101011001
0101000101110110011
从以上可以看出,m中a,b,c,d四个成员变量的地址应该是递增,它们对应的数值在二进制也是依次从高位往低位排。这说明,在内存中,二进制数10101010110010101000101110110011的高位数值在低地址的低bit位
。即
在小端系统中的结果:
1 | ●●●E:\workspace\code\alg_practice\test*master>$ .\a.exe |
m->a占3bit,数值是0x3
—–> 10101010110010101000101110110011
m->b占1bit,数值是0x0
—–> 10101010110010101000101110110
011
m->c占4bit,数值是0xb
—–> 1010101011001010100010111011
0011
m->d占5bit,数值是0xb
—–> 101010101100101010001011
10110011
说明小端字节序的位序也是小端的。