在不同的计算机体系结构中,对于数据(比特、字节、字等)的存储和传输机制有所不同;目前在各种体系结构的计算机中,主要采用的字节存储机制主要有两种:大端(Big-endian)和小端(Little-endian)。字节序,又称端序,尾序,英文:Endianness。在计算机科学领域中,字节序是指存放多字节数据的字节(byte)的顺序,典型的情况是整数在内存中的存放方式(小端)和网络传输的传输顺序(大端)。Endianness有时候也可以用指位序(bit)。
下面来看一个更具体的例子(我们只需要记住,整数在常见的x86cpu的架构中是按小端存储的,C/C++发送到网络的时候需要转换成大端)。
vs2017中,我们看一下uint32_t(无符号整数32位4字节)在内存中是什么样的:
这一串:07000000,所以是小端模式。
如果转换成大端呢?
前后2张图有什么不同?前者是直接打印uint32_t的内存地址,后者是通过evpp的Buffer类的AppendInt32函数,间接调用htonl把unsignedlong类型从主机序(x86cpu下是小端)转成网络字节序(大端)。
evpp(C++跨平台网络库)中2个转换函数细节如下:
typeImHeaderstruct{ Lengthuint32//thewholepdulength Versionuint16//pduversionnumber Flaguint16//notused ServiceIduint16// CommandIduint16// SeqNumuint16//包序号 Reverseduint16//保留 pbMessageproto.Message//消息体}像上面的头部,固定16字节。头4字节存放长度。假设我的服务端是用go开发的,按照大端解析,会造成什么后果?
第67行按照大端解析,本来客户端发送了数字7(代表数据部的长度是7),这边66行解析出来,自然就不对了,变成了117440512。所以后面也就没有办法正确的去取数据部的数据了,只能判断为坏包。
再来看一下内存:
我们看到开头:07000000,确实是小端,正确的大端应该是:00000007。
引用:
JAVA中所有的二进制文件都是按大端存储,这种存储方式也被称为networkorder。即在所有的平台上,如Mac、PC、UNIX等等运行JAVA,都不用考虑大小端的问题。麻烦的是不同语言开发的程序进行数据交换,如笔者最近的项目,二进制文件是由C生成的,通过redis消息通道以Json格式发过来,而C语言默认是小端模式,就涉及到大小端转换。有些平台(如Mac、IBM390)内置用的大端模式,其它一些平台内置用的小端模式(如Intel)。JAVA帮你屏蔽了各平台字节顺序的差异。
所以,java一般情况下不需要处理大小端问题,除非需要和其他语言如C/C++通信。
Java中虚拟机屏蔽了大小端问题,如果是Java之间通信则无需考虑。
只有在跨语言通信的场景下才需要处理大小端问题。比如使用Android使用Java,iOS使用Swift,服务端使用C++/GO,那么大家还是按照“网络字节序”的规矩发送(即大端),各个语言自行转换即可。
参考资料:
引用百度百科里面的一句话:
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐
下面再来看2个概念,加深理解。
通常在C/C++程序员眼中,char*普遍存在的意思是“一块内存”,甚至Java也具有byte[]表示原始内存的类型。真的有这么简单吗?
计算机的处理器并不是以单个字节读取和写入内存。而是以2、4、8、16甚至32字节的块访问内存(为了提升性能)。我们将处理器访问内存的大小称为内存访问粒度。
所以,这就有1个问题,假设我定义了一个结构体,大小为5会怎么办(除不尽2)?先看完一下对齐基础,下一节:结构体对齐中会有解答。
但是,请注意从地址1读取时会发生什么。由于该地址未均匀地落在处理器的内存访问边界上,因此处理器还有很多工作要做。这样的地址称为不对齐地址(所以#pragmapack指令会让程序变慢?)。因为地址1是未对齐的,所以具有两字节粒度的处理器必须执行额外的内存访问,从而减慢了操作速度。
最后,检查在具有四字节内存访问粒度的处理器上会发生什么,例如68030或PowerPC601;
正是因为处理器的内存访问不是按照我们理解的1个字节1个字节的读取,所以,编译器势必会为我们做一些事情从而优化性能。接下来,我们看一下结构体对齐,了解编译器在背后做的一些工作。
这一次,我拿macbookpro2017来实验。
一个无辜的结构体:
structHeader{chara;//1个字节intb;//4个字节charc;//1个字节};此结构的大小(以字节为单位)是多少?直观上看应该是1+4+1等于6,真的是吗?我们看一下它在内存中的布局:
右图是macos中,clang编译器的内存布局,我们发现,真正的大小并不是6个字节,而是按照结构体中最大的值(int=4字节)对齐,即3*4=12。然后冗余。我们利用clion来调试(变量上悬浮->右击->ShowinMemoryView)确定一下:
确实是这样,那会造成2个问题:
那怎么办?
如果您不了解并解决软件中的对齐问题,则以下情况的严重程度从高到低都是可能的:
简单点说,错误的对齐方式会造成内存浪费,性能降低,甚至是解析错误造成崩溃。
验证的:
未验证的:
正确的做法,示例1:
structHeader{ intb;//4个字节 chara;//1个字节 charc;//1个字节 int16_treserved;//2个字节,对齐};示例2(所以,在我们自定义协议时,特别是业务头中的各个字段类型可不能随便设定,而是要根据最大的值的整数倍放置):
typeImHeaderstruct{ Lengthuint32//thewholepdulength Versionuint16//pduversionnumber Flaguint16//notused ServiceIduint16// CommandIduint16// SeqNumuint16//包序号 Reverseduint16//保留 pbMessageproto.Message//消息体}