首先介绍大端、小端字节序。

注意是字节序列,这种纠结往往是指:某个变量占有偶数个字节,针对其字节序列的解读方式存在某种顺序讲究。

大端小端的说法不存在与字节内部。比如:11010001 就不用纠结是 1101 0001 还是 0001 1101,他就是 1Byte(8bits),无所谓大小端。(因为字节是计算机计算储存的基本单位,一个 ASCII 字符也就1字节,粒度再缩小的话,意义也就不大了)

大小端的 教科书定义:在大多数实际应用中,网络传输的字节序用大端,计算内部存储用小端。

宇节的排列方式有两个通用规则。例如,将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。 -WikiPedia

讲点人话:计算机中存储的是小端,符合人类阅读和计算(比如方便进制转换)的方式就是大端

所谓符合人类阅读和计算的方式,即人们阅读和计算的合适方式是:高地址索引位在前,低地址索引位在后,比如:

11010000 00000001 高地址索引位在前 11010000 00000001 对应的二进制为:2^0 + 2^12 + 2^14 + 2^15 = 53249

再说说指针(*p),其实就是一种解读方式。指针解引用取值(*p)和指针作运算(p+i) 时,都会依赖于其类型长度作为对应的步长。所谓改变指针类型也就是改变这个地址的解读方式(也就是改变他解读时的步长。)

通过指针类型转换,我们可以非常纯粹地干涉内存的解读方式。比如,一块连续的内存地址里面放的是字节序:1 2 3(每个数字代表一个字节),那么通过强转指针类型,要么按一个字节来读内存认作一个数,要么按两个字节来读内存认作一个数等等。

但是操作的过程中需要注意到,这里存在着大小端的问题,如果不注意到这一点,强制转换出来的数可能非常奇怪,以至于让人认为这是一个野指针或未定义的地址块。如何合理的利用指针结合大小段解读内存里面的数据?举个简单的例子:

1
2
char queryBuffer[] = {0 , 0 , 2 , 98};
cout << *((uint32_t*)queryBuffer + 1);

解读: 在C语言中,charList 将会是数组首地址的指针,其值为首地址,接下来第二个值 0 的地址即为索引位更高的地址。而在进行指针类型转换时,第一个0将会被作为高位读进去,符合人类阅读计算方式,所以默认强转指针后解引用出来是大端。

queryBuffer 是指针,值是数组首地址,因为是char类型,按1个字节解读,即: queryBuffer + 1 时,步长为1字节, (uint32_t*)queryBuffer 是指针,但是这里做了指针类型强制转换,按照4个字节解读(32位),即: queryBuffer + 1 时,步长为4字节,由于电脑内部是小端字节序,前四个字节 0, 0, 1, 98。那么 98 解读为高位地址:

  • 按大端解读:01100010, 00000001, 00000000, 00000000,十进制为1644232704
  • 按小端解读:00000000,00000000,00000001,01100010,十进制为354

那么,既然有大小端的问题,那如何便捷地进行大小端地址转换?

请参照 How do I convert between big-endian and little-endian values in C++?

完。