大小端及其影响

大小端

  • 起源
  • 大小端
  • 为什么处理器多少情况下选小端序
  • 实例
    • JVM中各种架构CPU地址处理

起源

《格列佛游记》两个国家长达36个月战争,一国支持鸡蛋大端开启,另一国支持小端开启,因此战争爆发。由此可见秦始皇统一度量衡的重要性。计算机字节顺序该怎么定义呢,鸡蛋问题又来了。计算机多数情况下CPU存储数据是小端序,网络传输、文件存储是大端序。

大小端

大端序符合我们阅读习惯,高位靠前低位靠后。小端序数据高位存放在地址高位,数据低位存放在地址低位
大小端及其影响_第1张图片

为什么处理器多少情况下选小端序

计算机读取从地址低位往高位读,如果是大端序,先读到的就是数据高位,做加法运算,先算高位,此时你还不确定低位是否有进位,所以把32位都加载进来,否则进位进位设计困难。用小端,只需要一个8位加法器就可以从低到高循环加上所有字节就可以了。强制类型转换时,左移刚好移除低位。
大端序,符号位刚好是第一个字节,方便知道数据正负和大小。

实例

32位系统上存储int类型值int i =0x1122;地址指针char* p =char*(&i)

//X86架构中(小端序)
//低位存储的值
p[0]=0x22;
//高位存储的值
p[1]=0x11;
//PowerC中(大端序)
//低位存储的值
p[0]=0x11;
//高位存储的值
p[1]=0x22;
//java中可以这样获取系统大小端
ByteOrder byteOrder = ByteOrder.nativeOrder();
System.out.println(byteOrder);//通常LITTLE_ENDIAN

模拟小端到大端的转换

//系统中如何存储n的
	int n = 0x12345678;
	char* p = (char *)&n;
	printf("[0]:0x%x",p[0]);
	printf("[1]:0x%x",p[1]);
	printf("[2]:0x%x",p[2]);
	printf("[3]:0x%x\n",p[3]);
	printf("n的值%d\n",n);
	//地址中存储结果,低地址端低数据位,高地址端高数据位,小端存储模式
	[0]:0x78[1]:0x56[2]:0x34[3]:0x12
	//数据值(0x12345678的十进制)
	n的值305419896
	//数据移位整合操作n=0x78563412
	//n = htonl(n);//也行
	n = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
	//新的n在内存中存储
	printf("[0]:0x%x\n",p[0]);
	printf("[1]:0x%x\n",p[1]);
	printf("[2]:0x%x\n",p[2]);
	printf("[3]:0x%x\n",p[3]);
	printf("位移之后的数据n:%d\n",n);
	[0]:0x12
	[1]:0x34
	[2]:0x56
	[3]:0x78
	位移之后的数据n:2018915346

JVM中各种架构CPU地址处理

由于CPU厂商选择大小端序不统一,所以JVM中涉及了各种架构CPU数据处理文件。java是大端序,class文件作为文件更是大端序,解析字节码的JVM要遵顼cpu架构的,x86小端序,这样大小端不一致导致数据错误。
假如二进制文件data.txt中存储着整数n=6553,内存布局是这样的:
在这里插入图片描述

FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL){
		perror("fopen");
		return -1;
	}
 
	int a = 0;
	fread(&a, sizeof(a), 1, pf);
	printf("读取到的数据为: %d", a);
 
	fclose(pf);
	pf = NULL;

大小端及其影响_第2张图片
读取结果怎么会是39193,这不是0x9919的值么
大小端及其影响_第3张图片
我们本意传输的是6553这个int值现在被系统小端存储模式误解了,地址高位对应数据高位,地址低位对应数据低位所以原本1999的19是数据高位,99低位,现在99被当作高位,19当作低位,自然就读成39193了。假如现在解释器读到常量池索引6553位置,把这个索引值读入JVM中,就成了39193了,就会出现错误要么找到错误的常量,要么索引越界。就需要小端转大端,既然内存中存的0x19_0x99,直接拿出来就是大端,是16字节,这样低位左移8位加上高位。但每一个字节取出时必须转成无符号整数u2,否则高位字节取出会被当作0XFFFFFF_ 。没有u2的话一般用掩码来mask=0xFF,这样取出的都是16位无符号整数

/**如果cpu是小端序,低地址位左移8位与上高位,就是需要转换的无符号整数
*/
static inline u2 get_Java_u2(address p) {
    return (u2(p[0]) << 8) | u2(p[1]);
  }
/**如果大端序就不需要转换,取地址不需要移位。低地址位存的是数值高位
*/
static inline u2 get_native_u2(address p){
    unaligned *up = (unaligned *) p;
    return up->us;
  }

读取二进制字节流,模拟字节码文件。

#include 

int main(void) {

	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL){
		perror("fopen");
		return -1;
	}
	int a = 0;
	int mask = 0xFF;
	//char类型指针p指向a的首地址
	char * p = (char*)&a;
	fread(&a, sizeof(a), 1, pf);
	printf("读取到的数据为: %d\n", a);
	printf("[0]:0x%x\n",p[0]);
	printf("[1]:0x%x\n",p[1]&mask);
	printf("[2]:0x%x\n",p[2]);
	printf("[3]:0x%x\n",p[3]);
	a = ((p[0]&mask) << 8 | (p[1]&mask));
	printf("转换之后的数据为: %d", a);
 
	fclose(pf);
	pf = NULL;
}

写入文件

FILE* pf = fopen("D:\\data.txt", "wb");
if (pf == NULL)
{
	perror("fopen");
	return -1;
}
 
int a = 100;
fwrite(&a, sizeof(a), 1, pf);
 
fclose(pf);
pf = NULL;

你可能感兴趣的:(操作系统,Java虚拟机,jvm,java)