字节序
字节序指多字节数据在计算机内存储或者网络上传输时各字节的顺序。(来源:百度百科)
为了方便,逻辑上将字节序列里左边的字节称为高字节,右边的字节称为低字节,从左到右,由高到低,这样符合数学上的思维习惯,左边是高位,右边是地位。
大端模式与小端模式
由于每个字节在内存中都是有地址的,并且内存的地址是顺序排列的,当我们在内存中保存数据时:
如果,高字节存放在低地址,低字节存放在高地址,则为大端模式(big-endian)。
如果,低字节存放在低地址,高字节存放在高地址,则为小端模式(little-endian)。
数据从内存保存到文件(或发送到网络上)时,会受到内存的大端模式与小端模式的影响。
数据从文件读取到(或从网络接收到)内存时,需要知道之前是先保存的(或是先发送的)高字节还是低字节。
C++示例代码1
//int 占 4 个字节,short 占 2 个字节
int main()
{
printf("在栈上分配内存\n");
int a = 0x11223344;
short b = 0x5566;
short c = 0x7788;
unsigned char *pa = (unsigned char *)&a;
unsigned char *pb = (unsigned char *)&b;
unsigned char *pc = (unsigned char *)&c;
printf("pa 0x%p 0x%x\n", pa, a);
printf("pb 0x%p 0x%x\n", pb, b);
printf("pc 0x%p 0x%x\n", pc, c);
printf("按字节序打印所有字节(高字节->低字节)\n");
printf("a0 0x%x\n", (a & 0xFF000000) >> (3 * 8));
printf("a1 0x%x\n", (a & 0x00FF0000) >> (2 * 8));
printf("a2 0x%x\n", (a & 0x0000FF00) >> (1 * 8));
printf("a3 0x%x\n", (a & 0x000000FF));
printf("b0 0x%x\n", (b & 0xFF00) >> (1 * 8));
printf("b1 0x%x\n", (b & 0x00FF));
printf("c0 0x%x\n", (c & 0xFF00) >> (1 * 8));
printf("c1 0x%x\n", (c & 0x00FF));
printf("根据地址顺序打印所有字节(低地址->高地址)\n");
for (int i = 0; i < 4; i++) {
printf("pa[%d] 0x%p 0x%02x\n", i, pa + i, pa[i]);
}
for (int i = 0; i < 2; i++) {
printf("pb[%d] 0x%p 0x%02x\n", i, pb + i, pb[i]);
}
for (int i = 0; i < 2; i++) {
printf("pc[%d] 0x%p 0x%02x\n", i, pc + i, pc[i]);
}
return 0;
}
示例代码1运行结果
在栈上分配内存
pa 0x007ffe24 0x11223344
pb 0x007ffe22 0x5566
pc 0x007ffe20 0x7788
按字节序打印所有字节(高字节->低字节)
a0 0x11
a1 0x22
a2 0x33
a3 0x44
b0 0x55
b1 0x66
c0 0x77
c1 0x88
根据地址顺序打印所有字节(低地址->高地址)
pa[0] 0x007ffe24 0x44
pa[1] 0x007ffe25 0x33
pa[2] 0x007ffe26 0x22
pa[3] 0x007ffe27 0x11
pb[0] 0x007ffe22 0x66
pb[1] 0x007ffe23 0x55
pc[0] 0x007ffe20 0x88
pc[1] 0x007ffe21 0x77
示例代码1结果分析
a、b、c 在内存中的排列情况:
---------------------------------------------------
|低地址 -> 高地址|
---------------------------------------------------
|....|0x88|0x77|0x66|0x55|0x44|0x33|0x22|0x11|....|
---------------------------------------------------
a、b、c 是在栈中分配的,可以看到内存地址是连续的,且 a 的地址相对较高,c 的地址相对较低。
从 a、b、c 在内存中的排列情况来看,本示例是在小端模式系统下运行的。
C++示例代码2
//int 占 4 个字节,short 占 2 个字节
int main()
{
printf("在堆上分配内存\n");
int *ap = new int(0x11223344);
short *bp = new short(0x5566);
short *cp = new short(0x7788);
printf("ap 0x%p 0x%x\n", ap, *ap);
printf("bp 0x%p 0x%x\n", bp, *bp);
printf("cp 0x%p 0x%x\n", cp, *cp);
printf("按字节序打印所有字节(高字节->低字节)\n");
printf("ap0 0x%x\n", (*ap & 0xFF000000) >> (3 * 8));
printf("ap1 0x%x\n", (*ap & 0x00FF0000) >> (2 * 8));
printf("ap2 0x%x\n", (*ap & 0x0000FF00) >> (1 * 8));
printf("ap3 0x%x\n", (*ap & 0x000000FF));
printf("bp0 0x%x\n", (*bp & 0xFF00) >> (1 * 8));
printf("bp1 0x%x\n", (*bp & 0x00FF));
printf("cp0 0x%x\n", (*bp & 0xFF00) >> (1 * 8));
printf("cp1 0x%x\n", (*bp & 0x00FF));
printf("根据地址顺序打印所有字节(低地址->高地址)\n");
unsigned char *p = (unsigned char *)ap;
for (int i = 0; i < 4; i++) {
printf("ap[%d] 0x%p 0x%02x\n", i, p + i, p[i]);
}
p = (unsigned char *)bp;
for (int i = 0; i < 2; i++) {
printf("bp[%d] 0x%p 0x%02x\n", i, p + i , p[i]);
}
p = (unsigned char *)cp;
for (int i = 0; i < 2; i++) {
printf("cp[%d] 0x%p 0x%02x\n", i, p + i, p[i]);
}
return 0;
}
示例代码2运行结果
在堆上分配内存
ap 0x1e059010 0x11223344
bp 0x1e059020 0x5566
cp 0x1e059030 0x7788
按字节序打印所有字节(高字节->低字节)
ap0 0x11
ap1 0x22
ap2 0x33
ap3 0x44
bp0 0x55
bp1 0x66
cp0 0x55
cp1 0x66
根据地址顺序打印所有字节(低地址->高地址)
ap[0] 0x1e059010 0x44
ap[1] 0x1e059011 0x33
ap[2] 0x1e059012 0x22
ap[3] 0x1e059013 0x11
bp[0] 0x1e059020 0x66
bp[1] 0x1e059021 0x55
cp[0] 0x1e059030 0x88
cp[1] 0x1e059031 0x77
示例代码2结果分析
a、b、c 在内存中的排列情况:
-------------------------------------------------------------
|低地址 -> 高地址|
-------------------------------------------------------------
|....|0x44|0x33|0x22|0x11|....|0x66|0x55|....|0x88|0x77|....|
-------------------------------------------------------------
在堆上分配内存和在栈上分配内存是不一样的,和示例1相比,这里 a、b、c 在内存中的地址是不连续的,且 a 的地址相对较低,c 的地址相对较高。
不管是在堆上还是在栈上,变量首地址处的字节都是该变量最低地址处的字节,且内容保持一致(如示例1中的 pa[0] 和示例2中的 ap[0])。
C++示例代码3
//int 占 4 个字节,short 占 2 个字节
int main()
{
typedef struct {
int a;
short b;
short c;
} Endian;
printf("结构体里的成员变量\n");
Endian endian;
endian.a = 0x11223344;
endian.b = 0x5566;
endian.c = 0x7788;
printf("endian 0x%p\n", &endian);
printf("endian.a 0x%p 0x%x\n", &endian.a, endian.a);
printf("endian.b 0x%p 0x%x\n", &endian.b, endian.b);
printf("endian.c 0x%p 0x%x\n", &endian.c, endian.c);
unsigned char *p = (unsigned char *)(&endian);
for (size_t i = 0; i < sizeof(Endian); i++) {
printf("endian[%d] 0x%p 0x%02x\n", i, p + i, p[i]);
}
Endian *pendian = new Endian();
pendian->a = 0x11223344;
pendian->b = 0x5566;
pendian->c = 0x7788;
printf("pendian 0x%p\n", pendian);
printf("pendian->a 0x%p 0x%x\n", &pendian->a, pendian->a);
printf("pendian->b 0x%p 0x%x\n", &pendian->b, pendian->b);
printf("pendian->c 0x%p 0x%x\n", &pendian->c, pendian->c);
p = (unsigned char *)pendian;
for (size_t i = 0; i < sizeof(Endian); i++) {
printf("pendian[%d] 0x%p 0x%02x\n", i, p + i, p[i]);
}
printf("文件读写\n");
ofstream ofile("test", ios::binary | ios::trunc);
ofile.write((char *)p, sizeof(Endian));
ofile.close();
unsigned char buffer[sizeof(Endian)] = {0};
ifstream ifile("test", ios::binary);
ifile.read((char *)buffer, sizeof(Endian));
ifile.close();
for (size_t i = 0; i < sizeof(Endian); i++) {
printf("buffer[%d] 0x%p 0x%02x\n", i, buffer + i, buffer[i]);
}
return 0;
}
示例代码3运行结果
结构体里的成员变量
endian 0x007ffe24
endian.a 0x007ffe24 0x11223344
endian.b 0x007ffe28 0x5566
endian.c 0x007ffe2a 0x7788
endian[0] 0x007ffe24 0x44
endian[1] 0x007ffe25 0x33
endian[2] 0x007ffe26 0x22
endian[3] 0x007ffe27 0x11
endian[4] 0x007ffe28 0x66
endian[5] 0x007ffe29 0x55
endian[6] 0x007ffe2a 0x88
endian[7] 0x007ffe2b 0x77
pendian 0x008b8ea0
pendian->a 0x008b8ea0 0x11223344
pendian->b 0x008b8ea4 0x5566
pendian->c 0x008b8ea6 0x7788
pendian[0] 0x008b8ea0 0x44
pendian[1] 0x008b8ea1 0x33
pendian[2] 0x008b8ea2 0x22
pendian[3] 0x008b8ea3 0x11
pendian[4] 0x008b8ea4 0x66
pendian[5] 0x008b8ea5 0x55
pendian[6] 0x008b8ea6 0x88
pendian[7] 0x008b8ea7 0x77
文件读写
buffer[0] 0x007ffd28 0x44
buffer[1] 0x007ffd29 0x33
buffer[2] 0x007ffd2a 0x22
buffer[3] 0x007ffd2b 0x11
buffer[4] 0x007ffd2c 0x66
buffer[5] 0x007ffd2d 0x55
buffer[6] 0x007ffd2e 0x88
buffer[7] 0x007ffd2f 0x77
示例代码3结果分析
a、b、c 在内存中的排列情况:
---------------------------------------------------
|低地址 -> 高地址|
---------------------------------------------------
|....|0x44|0x33|0x22|0x11|0x66|0x55|0x88|0x77|....|
---------------------------------------------------
a、b、c 在文件中的排列情况:
-----------------------------------------
|文件开头 -> 文件结尾|
-----------------------------------------
|0x44|0x33|0x22|0x11|0x66|0x55|0x88|0x77|
-----------------------------------------
不管是在栈上还是在堆上,a、b、c 作为结构体里的成员变量,在内存中的布局保持一致:内存地址连续,且 a 的地址相对较低,c 的地址相对较高。
像这样按地址顺序从低到高将整个结构体保存到文件(或在网络上发送),会受到大端模式还是小端模式的影响。
C++示例代码4
//int 占 4 个字节,float 占 4 个字节
int main()
{
union {
int a;
unsigned char c[sizeof(int)];
} int_endian_test = {0};
int_endian_test.a = 0xEE;
printf("a 0x%x\n", int_endian_test.a);
printf("c[0] 0x%x\n", int_endian_test.c[0]);
if (int_endian_test.c[0] == (unsigned char)int_endian_test.a) {
printf("int little endian\n");
} else if (int_endian_test.c[sizeof(int)-1] == (unsigned char)int_endian_test.a) {
printf("int big endian\n");
} else {
printf("int unknown endian\n");
}
union {
int b;
float f;
unsigned char d[sizeof(float)];
} float_endian_test = {0};
float_endian_test.f = 12.34f;
printf("b 0x%x\n", float_endian_test.b);
printf("d[0] 0x%x\n", float_endian_test.d[0]);
if (float_endian_test.d[0] == (unsigned char)float_endian_test.b) {
printf("float little endian\n");
} else if (float_endian_test.d[sizeof(int)-1] == (unsigned char)float_endian_test.b) {
printf("float big endian\n");
} else {
printf("float unknown endian\n");
}
return 0;
}
示例代码4运行结果
a 0xee
c[0] 0xee
int little endian
b 0x414570a4
d[0] 0xa4
float little endian
示例代码4结果分析
本示例常用于判断大端模式还是小端模式。
后记
以上代码结果均运行于 win10 64 位系统下,编译工具 QT MinGW 32。