字节序指多字节数据在计算机内存储或者网络上传输时各字节的顺序。(来源:百度百科)
为了方便,逻辑上将字节序列里左边的字节称为高字节,右边的字节称为低字节,从左到右,由高到低,这样符合数学上的思维习惯,左边是高位,右边是地位。
由于每个字节在内存中都是有地址的,并且内存的地址是顺序排列的,当我们在内存中保存数据时:
如果,高字节存放在低地址,低字节存放在高地址,则为大端模式(big-endian)。
如果,低字节存放在低地址,高字节存放在高地址,则为小端模式(little-endian)。
数据从内存保存到文件(或发送到网络上)时,会受到内存的大端模式与小端模式的影响。
数据从文件读取到(或从网络接收到)内存时,需要知道之前是先保存的(或是先发送的)高字节还是低字节。
//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; }
在栈上分配内存 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
a、b、c 在内存中的排列情况: --------------------------------------------------- |低地址 -> 高地址| --------------------------------------------------- |....|0x88|0x77|0x66|0x55|0x44|0x33|0x22|0x11|....| ---------------------------------------------------
a、b、c 是在栈中分配的,可以看到内存地址是连续的,且 a 的地址相对较高,c 的地址相对较低。
从 a、b、c 在内存中的排列情况来看,本示例是在小端模式系统下运行的。
//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; }
在堆上分配内存 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
a、b、c 在内存中的排列情况: ------------------------------------------------------------- |低地址 -> 高地址| ------------------------------------------------------------- |....|0x44|0x33|0x22|0x11|....|0x66|0x55|....|0x88|0x77|....| -------------------------------------------------------------
在堆上分配内存和在栈上分配内存是不一样的,和示例1相比,这里 a、b、c 在内存中的地址是不连续的,且 a 的地址相对较低,c 的地址相对较高。
不管是在堆上还是在栈上,变量首地址处的字节都是该变量最低地址处的字节,且内容保持一致(如示例1中的 pa[0] 和示例2中的 ap[0])。
//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; }
结构体里的成员变量 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
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 的地址相对较高。
像这样按地址顺序从低到高将整个结构体保存到文件(或在网络上发送),会受到大端模式还是小端模式的影响。
//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; }
a 0xee c[0] 0xee int little endian b 0x414570a4 d[0] 0xa4 float little endian
本示例常用于判断大端模式还是小端模式。
以上代码结果均运行于 win10 64 位系统下,编译工具 QT MinGW 32。