hello,小伙伴们好,我是江湖人送外号[道格牙]的子牙老师。
这篇文章,咱们讨论的问题是数据在计算机中是如何存储的。为什么要讨论这个问题呢?因为在手写JVM的过程中,我们需要实现所有的数据类型,比如说Java中的八种基本类型。那在实现的过程中,Java的八种基本类型在JVM的内存模型中应该如何存储就是我们需要考虑的问题。我们只能通过研究操作系统这块的机制来寻找答案。其实这个知识点也是大家能够真正学明白指针的关键。
本篇文章,你可能会看到很多与你的固有认知不太一样的观点。但是我都会给出证据来证明我的观点:
1、内存不分正负,数据是正是负取决于用的程序
2、正数可以当负数用,负数也可以当正数用
3、原码反码补码的出现,其实是对一个规律的反向推理
4、指针不是指向内存的地址,而是一个个不同大小的容器
正数负数零
如果我们程序中定义了四个这样的变量,它们的值在内存中是如何存储的呢?
先上图,等下解释。这里咱们就不考虑大小端存储问题了。
说明一下,char在C语言中是占一个字节,在Java是占两个字节。我这个图画的是char占一个字节。
这里面最奇怪的是v3,它在内存中是0xFF,跟正数的255是一样的。为什么会这样呢?我先用原码反码补码的知识告诉你答案。等下再告诉你本质。
负数在内存中的存储格式是这样算出的:内存中存储的是负数的反码。反码为补码加一。正数的反码与原码相同。负数的反码为原码的符号位不变,其他位取反。是不是有点晕。
对于原码反码补码,不知道大家学完后什么感觉。我上大学的时候学到这里,觉得好奇葩好复杂。直到随着对计算机底层的认知逐渐深入,了解到它的本质后,豁然开朗,根本就不需要这样去算,一眼就能看出来。下一PA,告诉你本质。
总结来说:从程序角度,有正数、负数、零之分。但是从内存的角度,它是没有这个概念的。内存中存储的数据,这个数据的意义是由程序员来定义的。你希望它是正数,就以正数的方式读取。你希望它是负数,就以负数的方式读取。
揭秘本质
咱们不说浮点型,就说正数。如果内存存储机制由你来设计,需要存储正数负数零,你会如何设计?
经常看我文章的小伙伴可能比较奇怪为什么我总是问:如果是你你会如何设计?因为我希望让大家站在设计者的角度学习思考。从我自身的角度来说,如果我只是站在一个学习者的角度去研究JVM,可能对很多东西的理解就达不到今天这个深度。建议大家学习这个思维,站在设计者的角度,而不是学习者的角度。
上图,进制十六卦图。
大家是否看出规律了:1、正数负数各占一半;2、负数的值根本就不需要算,一眼就能看出来。比如-2,如果是4位是0xE,4位是0xFE,8位就是0xFE…
这就是本质!原码反码补码就是根据这个本质反推出来的。这就是设计者思维!
谈越界
如果你以前对数据越界没有概念。前面讲了那么多,是不是让你对这个词的理解不一样了呢!
拿十进制来说,9+1=10。但是在计算机里面,我们用的数据是有边界的,即数据宽度。比如一个char占一个字节,下面的代码,如果你来设计内存存储,你会怎么做呢
好像很为难是吧。计算机目前的做法很简单粗暴,照样进一,就变成了0x100。但是char只能存放一个字节,那就存0x00。这就是数据越界、数据丢失,程序依然能正常运行。所以定义变量的时候,选择合适的容器很重要。
其实想用Java写出BUG还挺难的,不晓得有些小伙伴是怎么做到面向BUG编程的,成功躲开所有正确选项。Java的数据越界,IDEA会马上提示,但是C++的工具是不会的。这就是底层为什么难学。它是跟计算机的设计思想紧密相连的。
结语
我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。喜欢分享硬核知识,如果你也喜欢研究底层,喜欢硬核知识,关注我:硬核子牙。
·