一、字节序
字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。
在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为int
的变量x
地址为0x100
,那么其对应地址表达式&x
的值为0x100
。且x
的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103
位置
二、大端与小端
根据x在连续的4字节内存中存储的顺序,字节序分为大端序(Big Endian) 与 **小端序(Little Endian)**两类,数值0x1234
使用两个字节储存:高位字节是0x12
,低位字节是0x34
- 大端序:高位字节在前,低位字节在后,人类读写数值的方法
- 小端序:低位字节在前,高位字节在后,通常x86架构以小端序存储数据
如图所示:
- Big Endian 是指低地址端 存放 高位字节。
- Little Endian 是指低地址端 存放 低位字节
1、为啥有大小端之分?
答案是,计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。在计算机内部,小端序被广泛应用于现代性 CPU 内部存储数据;而在其他场景譬如网络传输和文件存储使用大端序。
使用小端序时不移动字节就能改变 number 占内存的大小而不需内存地址起始位。比如我想把四字节的 int32 类型的整型转变为八字节的 int64 整型,只需在小端序末端加零即可。
1
2
|
44 33 22 11
44 33 22 11 00 00 00 00
|
上述扩展或缩小整型变量操作在编译器层面非常有用,但在网络协议层非也。
在网络协议层操作二进制数字时约定使用大端序,大端序是网络字节传输采用的方式。因为大端序最高有效字节排在首位(低地址端存放高位字节),能够按照字典排序,所以我们能够比较二进制编码后数字的每个字节。
1
|
fmt.Println(bytes.Equal([]byte("Go"), []byte("Go"))) // true
|
2、为什么要注意字节序?
当程序要与别的程序交互的时候,就涉及到字节序的处理。字节序的处理原则就是
“只有读取的时候,才必须区分字节序,其他情况都不用考虑。”
处理器读取外部数据的时候,必须知道数据的字节序,将其转成正确的值。然后,就正常使用这个值,完全不用再考虑字节序。
即使是向外部设备写入数据,也不用考虑字节序,正常写入一个值即可。外部设备会自己处理字节序的问题。
三、网络序和主机序
网络字节序:TCP/IP各层协议将字节序定义为 Big Endian,因此TCP/IP协议中使用的字节序是大端序。
主机字节序:整数在内存中存储的顺序,现在 Little Endian 比较普遍。(不同的 CPU 有不同的字节序)
在进行网络通信时 通常需要调用相应的函数进行主机序和网络序的转换。Berkeley socket API 定义了一组转换函数,用于16和32bit整数在网络序和本机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。
四、各语言关于字节序的处理
1、Java
JAVA字节序默认是大端序(Big Endian)。由于JVM会根据底层的操作系统和CPU自动进行字节序的转换,所以我们使用java进行网络编程,几乎感觉不到字节序的存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
public class JVMEndianTest {
public static void main(String[] args) {
int x = 0x01020304;
ByteBuffer bb = ByteBuffer.wrap(new byte[4]);
bb.asIntBuffer().put(x);
String ss_before = Arrays.toString(bb.array());
System.out.println("默认字节序 " + bb.order().toString() + "," + " 内存数据 " + ss_before);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asIntBuffer().put(x);
String ss_after = Arrays.toString(bb.array());
System.out.println("修改字节序 " + bb.order().toString() + "," + " 内存数据 " + ss_after);
}
}
|
2、C++
C/C++ 存储数据时的字节序依赖所在平台的CPU,所以可以通过C/C++程序判定机器的端序:
1
2
3
4
5
6
7
8
|
void Endianness()
{
int a = 0x12345678;
if( *((char*)&a) == 0x12)
cout << "Big Endian" << endl;
else
cout << "Little Endian" << endl;
}
|
3、go
Go中处理大小端序的代码位于 encoding/binary
,包中的全局变量BigEndian用于操作大端序数据,LittleEndian用于操作小端序数据,这两个变量所对应的数据类型都实行了ByteOrder接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
package main
import (
"encoding/binary"
"fmt"
"unsafe"
)
const INT_SIZE = int(unsafe.Sizeof(0)) //64位操作系统,8 bytes
//判断我们系统中的字节序类型
func systemEdian() {
var i = 0x01020304
fmt.Println("&i:",&i)
bs := (*[INT_SIZE]byte)(unsafe.Pointer(&i))
if bs[0] == 0x04 {
fmt.Println("system edian is little endian")
} else {
fmt.Println("system edian is big endian")
}
fmt.Printf("temp: 0x%x,%v\n",bs[0],&bs[0])
fmt.Printf("temp: 0x%x,%v\n",bs[1],&bs[1])
fmt.Printf("temp: 0x%x,%v\n",bs[2],&bs[2])
fmt.Printf("temp: 0x%x,%v\n",bs[3],&bs[3])
}
func testBigEndian() {
var testInt int32 = 0x01020304
fmt.Printf("%d use big endian: \n", testInt)
var testBytes []byte = make([]byte, 4)
binary.BigEndian.PutUint32(testBytes, uint32(testInt))
fmt.Println("int32 to bytes:", testBytes)
fmt.Printf("int32 to bytes: %x \n", testBytes)
convInt := binary.BigEndian.Uint32(testBytes)
fmt.Printf("bytes to int32: %d\n\n", convInt)
}
func testLittleEndian() {
var testInt int32 = 0x01020304
fmt.Printf("%x use little endian: \n", testInt)
var testBytes []byte = make([]byte, 4)
binary.LittleEndian.PutUint32(testBytes, uint32(testInt))
fmt.Printf("int32 to bytes: %x \n", testBytes)
convInt := binary.LittleEndian.Uint32(testBytes)
fmt.Printf("bytes to int32: %d\n\n", convInt)
}
func main() {
systemEdian()
fmt.Println("")
testBigEndian()
testLittleEndian()
}
|
输出:
&i: 0xc00000a0b8
system edian is little endian
temp: 0x4,0xc00000a0b8
temp: 0x3,0xc00000a0b9
temp: 0x2,0xc00000a0ba
temp: 0x1,0xc00000a0bb
16909060 use big endian:
int32 to bytes: [1 2 3 4]
int32 to bytes: 01020304
bytes to int32: 16909060
1020304 use little endian:
int32 to bytes: 04030201
bytes to int32: 16909060
五、参考文章
理解字节序
golang之大端序、小端序
字节序及 Go encoding/binary 库
字节顺序