網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
C語(yǔ)言-剖析數(shù)據(jù)是如何在內(nèi)存中存儲(chǔ)的(整型與浮點(diǎn)型)
作者:c鐵柱同學(xué) 更新時(shí)間: 2022-02-12 編程語(yǔ)言本篇主要是深度剖析整型與浮點(diǎn)型數(shù)據(jù)在內(nèi)存中是如何儲(chǔ)存與讀取的。弄明白數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式有助于我們更好的理解代碼。
1.整型在內(nèi)存中的存儲(chǔ)
1.1有符號(hào)整型
在研究整型變量的存儲(chǔ)之前,我們要知道,整型有以下幾種類型
char signed char
unsigned char
short signed short [int]
unsigned short [int]
int signed int
unsigned int
long signed long [int]
unsigned long [int]
short 與long的全稱是short int 與long int,后面的int可以省略,但是為什么會(huì)有char這個(gè)類型呢?,我們知道char類型叫做字符型,它表示的是用來(lái)存儲(chǔ)一個(gè)字符的類型,把它作為整型是因?yàn)樽址趦?nèi)存中是以ASCII值儲(chǔ)存的,所以我們把char型也歸為整型。每一個(gè)類型又細(xì)分出signed和unsigned兩個(gè)類型,叫做有符號(hào)型和無(wú)符號(hào)型,在我們不寫有沒有符號(hào)時(shí),short,int,long這三個(gè)類型默認(rèn)是有符號(hào)類型,只有char類型在不寫時(shí)是有符號(hào)類型還是無(wú)符號(hào)類型是不確定的,取決于編譯器,我使用的是vs2022,這個(gè)編譯器下char默認(rèn)為signed char。
當(dāng)我們?cè)诖a中創(chuàng)建一個(gè)變量時(shí),我們知道要在內(nèi)存上開辟一塊空間來(lái)儲(chǔ)存這個(gè)變量,那么整型變量在內(nèi)存中到底是怎么存儲(chǔ)的呢?我們用代碼來(lái)看一下。
我們創(chuàng)建了a與b兩個(gè)整型變量,然后把3賦給a,-1賦給b,然后我們?cè)僭趦?nèi)存中尋找a與b,如上圖,靠左邊的第一個(gè)行就是b的地址與b內(nèi)存儲(chǔ)的數(shù)值,靠右邊的第一行就是a的地址與a中存儲(chǔ)的元素,vs在展示內(nèi)存時(shí),為了方便展示,顯示的是十六進(jìn)制的數(shù)據(jù),而數(shù)據(jù)在內(nèi)存中是以二進(jìn)制存儲(chǔ)的,一個(gè)字節(jié)的大小是八個(gè)比特位,八個(gè)比特位有2^8種數(shù)=16^2,剛好可以用兩位十六進(jìn)制數(shù)表示,所以在上圖中存放數(shù)據(jù)的位置每?jī)晌槐硎疽粋€(gè)字節(jié),因?yàn)槲覀兇娣诺氖钦驼妓膫€(gè)字節(jié),所以我把內(nèi)存顯示的寬度調(diào)為四列,方便我們觀察。
我們知道,計(jì)算機(jī)中的有符號(hào)數(shù)有三種表示方法:原碼,反碼,補(bǔ)碼。
這三種方法都有符號(hào)位和數(shù)值位,他們二進(jìn)制位的第一位就是符號(hào)位,0表示正數(shù),1表示負(fù)數(shù),其他位就是數(shù)值位。那么這三種表示方法都是怎么得到的呢?
我們直接給出結(jié)論:正數(shù)的原碼,反碼,補(bǔ)碼相同,負(fù)數(shù)的原,反,補(bǔ)碼由以下規(guī)則計(jì)算
原碼:直接把數(shù)轉(zhuǎn)換成二進(jìn)制序列
反碼:原碼的二進(jìn)制序列符號(hào)位不變,數(shù)值位按位取反
補(bǔ)碼:反碼的二進(jìn)制序列加一
現(xiàn)在我們知道了這三種表示形式都是怎么得到的,那么計(jì)算機(jī)在存儲(chǔ)時(shí)到底使用的是哪一種呢?我們來(lái)計(jì)算一下
首先,我們來(lái)算3
因?yàn)?是正數(shù),那么他的原,反,補(bǔ)碼都是相同的,為
(為了方便觀察,我會(huì)在后面每次展示二進(jìn)制序列時(shí)每隔八位打一個(gè)空格)
00000000 00000000 00000000 00000011--3(二進(jìn)制)
0 0 0 0 0 0 0 3 --3(十六進(jìn)制)
我們?cè)賮?lái)看-1
原碼:10000000 00000000 00000000 00000001-- -1(二進(jìn)制原碼)
反碼:11111111 11111111 11111111 11111110-- -1(二進(jìn)制反碼)
補(bǔ)碼:11111111 11111111 11111111 11111111-- -1(二進(jìn)制補(bǔ)碼)
補(bǔ)碼:f f f f f f f f -- -1(十六進(jìn)制補(bǔ)碼)
經(jīng)過(guò)一個(gè)簡(jiǎn)單的計(jì)算我們可以發(fā)現(xiàn),我們內(nèi)存中存儲(chǔ)的其實(shí)是補(bǔ)碼,那么為什么內(nèi)存存儲(chǔ)的是補(bǔ)碼呢?
因?yàn)镃PU只有加法器,當(dāng)我們想要計(jì)算比如1-1時(shí),計(jì)算機(jī)會(huì)模擬成1-(-1)這時(shí)候我們?nèi)绻迷a來(lái)計(jì)算,那么結(jié)果是這樣的
00000000 00000000 00000000 00000001--(1)
10000000 00000000 00000000 00000001--(-1)
10000000 00000000 00000000 00000010--(1+(-1))==(-2)?
我們發(fā)現(xiàn)這個(gè)值等于-2,是不正確的,如果我們使用補(bǔ)碼來(lái)計(jì)算
10000000 00000000 00000000 00000001--(-1)二進(jìn)制原碼
11111111 11111111 11111111 11111110--(-1)二進(jìn)制反碼
11111111 11111111 11111111 11111111--(-1)二進(jìn)制補(bǔ)碼
00000000 00000000 00000000 00000001--(1)二進(jìn)制補(bǔ)碼
100000000 00000000 00000000 00000000--(1+(-1))的二進(jìn)制補(bǔ)碼
00000000 00000000 00000000 00000000--(1+(-1))的二進(jìn)制原碼=0
我們發(fā)現(xiàn),使用補(bǔ)碼計(jì)算的結(jié)果是正確的,使用補(bǔ)碼還有一個(gè)好處
11111111 11111111 11111111 11111111--(-1)二進(jìn)制補(bǔ)碼
10000000 00000000 00000000 00000000--對(duì)(-1)的二進(jìn)制補(bǔ)碼按位取反
10000000 00000000 00000000 00000001--又得到了(-1)的原碼
即我們可以使用相同的方法完成原碼與補(bǔ)碼之間的轉(zhuǎn)換。
使用補(bǔ)碼可以將符號(hào)位和數(shù)值位統(tǒng)一處理,同時(shí),加法與減法也可以統(tǒng)一處理。
1.2無(wú)符號(hào)整型
以上我們就明白了整型數(shù)是怎么在內(nèi)存中存儲(chǔ)的,但是注意,我在定義a與b時(shí)使用的是int,我們說(shuō)每個(gè)整形都有兩個(gè)細(xì)分類型,分別是有符號(hào)整型與無(wú)符號(hào)整型,在我們前面沒有寫的時(shí)候默認(rèn)為有符號(hào)整型,那么無(wú)符號(hào)整型是怎么存儲(chǔ)的呢?
因?yàn)閏har類型比較小,所以我們以一個(gè)char類型為例
如果這是一個(gè)有符號(hào)的char,第一位是符號(hào)位
00000000-->0
00000001-->1
00000010-->2
00000011-->3
…………
01111111-->2^7-1-->127
10000000--不進(jìn)行運(yùn)算,直接翻譯為-128
10000001-->-(2^7-1)-->-127
10000010-->-126
…………
11111110-->-2
11111111-->-1
signed char的范圍為(-128~127)
如果這是一個(gè)無(wú)符號(hào)char,八個(gè)都是數(shù)值位
00000000-->0
00000001-->1
00000010-->2
00000011-->3
…………
01111111-->2^7-1-->127
10000000-->128
10000001-->129
10000010-->130
…………
11111110-->254
11111111-->2^8-1-->255
unsigned char的范圍為(0~255)
同理,其他整型的有符號(hào)型和無(wú)符號(hào)型也是可以這樣計(jì)算
1.3大小端字節(jié)序
我們知道了整型在內(nèi)存中的存儲(chǔ)方法后,現(xiàn)在在回到最開始的代碼,我們發(fā)現(xiàn)在存儲(chǔ)3時(shí)我們存儲(chǔ)的順序是03 00 00 00,這是為什么呢?難到是反著存的嗎?
?這與大小端字節(jié)序有關(guān),那么什么是大小端字節(jié)序呢,我們往下看
比如我現(xiàn)在要存放一個(gè)十六進(jìn)制數(shù)11 22 33 44,在內(nèi)存中現(xiàn)在有這么兩種方法
(1)
11 22 33 44
低地址 ---> 高地址
(2)
44 33 22 11
低地址 ---> 高地址
我們把方法(1)叫做大端字節(jié)序存儲(chǔ),即:把一個(gè)數(shù)的低位字節(jié)序(低位)內(nèi)容放在高地址處,高位字節(jié)序放在低地址處。
把方法(2)叫做小段字節(jié)序存儲(chǔ),即:把一個(gè)數(shù)的高位字節(jié)序(高位)內(nèi)容放在高地址處,低位字節(jié)序放在低地址處。
我們現(xiàn)在看一下當(dāng)前編譯器是采用了什么存儲(chǔ)模式
?我們可以看到,地址是由低到高的,而存儲(chǔ)順序也是由低位到高位的,所以是小段字節(jié)序存儲(chǔ)。
為什么會(huì)有大小端模式之分呢?這是因?yàn)樵谟?jì)算機(jī)系統(tǒng)中,我們是以字節(jié)為單位的,每個(gè)地址單元 都對(duì)應(yīng)著一個(gè)字節(jié),一個(gè)字節(jié)為8bit。但是在C語(yǔ)言中除了8 bit的char之外,還有16 bit的short型,32 bit的long型(要看具體的編 譯器),另外,對(duì)于位數(shù)大于8位 的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個(gè)字節(jié),那么必然存在著一個(gè)如何將多個(gè)字節(jié)安排的問(wèn)題。因此就 導(dǎo)致了大端存儲(chǔ)模式和小端存儲(chǔ)模式。 例如:一個(gè) 16bit 的 short 型 x ,在內(nèi)存中的地址為 0x0010 , x 的值為 0x1122 ,那么 0x11 為 高字節(jié), 0x22 為低字節(jié)。對(duì)于大端 模式,就將 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式, 剛好相反。我們常用的 X86 結(jié)構(gòu)是 小端模式,而 KEIL C51 則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以 由硬件來(lái)選擇是大端模式還是小端 模式。
2. 浮點(diǎn)數(shù)在內(nèi)存中的儲(chǔ)存
我們先來(lái)看這樣一段代碼
?
?我們現(xiàn)在來(lái)分析一下,為什么是這樣的情況呢?
要搞明白這個(gè)結(jié)果,我們就要先弄清楚浮點(diǎn)數(shù)在計(jì)算機(jī)內(nèi)部的表示方法。
根據(jù)國(guó)際標(biāo)準(zhǔn)IEEE(電氣和電子工程協(xié)會(huì)) 754,任意一個(gè)二進(jìn)制浮點(diǎn)數(shù)V可以表示成下面的形式:
? (-1)^S * M * 2^E (-1)^s表示符號(hào)位,當(dāng)s=0,V為正數(shù);當(dāng)s=1,V為負(fù)數(shù)。
? M表示有效數(shù)字,大于等于1,小于2。
? 2^E表示指數(shù)位。
?這是什么意思呢?,我們打個(gè)比方,比如我們現(xiàn)在要表示浮點(diǎn)數(shù)6.5
6.5 --(十進(jìn)制)
110.1 --(二進(jìn)制)
1.101*2^2 --(科學(xué)計(jì)數(shù)法)
(-1)^0*1.101*2^2 --(IEEE754)
按照IEEE754的規(guī)定,S=0,M=1.101,E=2。
那么在內(nèi)存中我們是怎么來(lái)存儲(chǔ)這些數(shù)的呢,我們以單精度浮點(diǎn)型為例
float占4個(gè)字節(jié),32個(gè)比特位,我們?nèi)绾蝸?lái)分配這32個(gè)bit位呢?
我們以一個(gè)*代表一個(gè)bit位,把32位的內(nèi)存這樣劃分:
* ******** ***********************
S(1) E(8) M(23)
IEEE754規(guī)定:第一個(gè)比特位用來(lái)存符號(hào)位,接著八位存指數(shù)E,剩下的23位存有效數(shù)M
64位的double類型
* *********** ****************************************************
S(1) E(11) M(52)
對(duì)與64位的double型,最高位是符號(hào)位S,接著十一位是指數(shù)E,剩下的52位存有效數(shù)M
在規(guī)定中M是大于1小于2的數(shù),那么我們可以知道M總是1.xxxxxxxx。也就是說(shuō)M的第一位永遠(yuǎn)是1,那么我們?cè)诖鎯?chǔ)是是不是可以不存這一位,只存小數(shù)點(diǎn)后面的位,然后在使用的時(shí)候再把這個(gè)1加上即可,這樣的話我們的23位存儲(chǔ)M的空間就可以多存儲(chǔ)一位數(shù)了,以此可以提高我們浮點(diǎn)數(shù)的精度。
在存儲(chǔ)指數(shù)E時(shí),首先,我們規(guī)定E是一個(gè)無(wú)符號(hào)數(shù),如果E為8位,它的取值就是0~255,若E為11位,取值就為0~2047,但是當(dāng)我們實(shí)際用來(lái)表示一個(gè)數(shù)時(shí),(比如小于1的浮點(diǎn)數(shù)),指數(shù)位E就會(huì)是負(fù)數(shù),這時(shí)候就出現(xiàn)了問(wèn)題,所以IEEE754規(guī)定,在存儲(chǔ)E時(shí),E的真實(shí)值必須加上一個(gè)中間數(shù),8位加127,11位加1023,然后再存入E區(qū)
在讀取指數(shù)E時(shí),又分為3種情況
(1)E不全為0或不全為1(正常情況)
把計(jì)算值減去127(或1023)得到真實(shí)值,再在M前面加上省去的那個(gè)1即可以還原
(2)E全為0
這時(shí)我們直接規(guī)定E的真實(shí)值為1-127或(1-1023),M前面不再加1,還原為0.xxxxx的小數(shù)
我們可以計(jì)算一下,E=0-127,這個(gè)數(shù)就是(-1)^S*M*2^(-127),可以想象的到這是一個(gè)非常小的數(shù),這是為了表示±0以及無(wú)限接近0的數(shù)。
(3)E全為1
E+127=255,E=128,我們知道2^32的值為42億多,那么2^128就是4個(gè)42億相乘,可以想象這也是一個(gè)非常大的值了,所以這時(shí)的M全為0,表示±無(wú)窮大
知道了以上這些內(nèi)容,我們?cè)倩剡^(guò)頭來(lái)看那個(gè)代碼
?首先是第一個(gè)printf,以整型打印n,結(jié)果是9,沒有問(wèn)題
再來(lái)看第二個(gè)printf,我們用*pFloat以浮點(diǎn)數(shù)的視角來(lái)訪問(wèn)這四個(gè)字節(jié),這時(shí)候電腦會(huì)認(rèn)為這里存的是浮點(diǎn)數(shù)。
00000000 00000000 00000000 00001001--9
0 00000000 00000000000000000001001--以浮點(diǎn)數(shù)的視角來(lái)看
S E M
這時(shí)這個(gè)二進(jìn)制序列就會(huì)以浮點(diǎn)數(shù)的規(guī)則被翻譯,我們發(fā)現(xiàn)它的E全為0,符號(hào)位為0,所以表示0.000000。所以打印的第二個(gè)值也沒有問(wèn)題了
我們?cè)賮?lái)看第三個(gè)printf,我們先是以浮點(diǎn)數(shù)的形式在這個(gè)地址存放一個(gè)9.0,再以整型的方式打印出來(lái)。
9.0-->1001.0-->1.001*2^3
E=3+127=130-->10000010
0 10000010 10010000000000000000000--以浮點(diǎn)數(shù)的方式存放9.0
S E M
我們?cè)侔堰@個(gè)數(shù)以整型的讀取方式讀出來(lái)的值就是1091567616
再看第四個(gè)printf,以浮點(diǎn)數(shù)的方式讀取剛剛存貯的9.0,結(jié)果自然也是9.0
以上就是本篇的全部?jī)?nèi)容,如果大家覺得我的文章對(duì)你有幫助,希望能夠給我點(diǎn)贊支持一下,鐵柱在這里謝謝大家了!!
原文鏈接:https://blog.csdn.net/qq_45967533/article/details/122860682
相關(guān)推薦
- 2021-12-16 .NET中的狀態(tài)機(jī)庫(kù)Stateless的操作流程_實(shí)用技巧
- 2022-07-11 MongoDB使用正則匹配、修改內(nèi)容
- 2023-01-20 Python使用窮舉法求兩個(gè)數(shù)的最大公約數(shù)問(wèn)題_python
- 2022-05-11 Qt編寫地圖之實(shí)現(xiàn)經(jīng)緯度坐標(biāo)糾偏_C 語(yǔ)言
- 2022-08-04 基于Python實(shí)現(xiàn)二維圖像雙線性插值_python
- 2022-03-30 C語(yǔ)言實(shí)現(xiàn)猜數(shù)字小項(xiàng)目_C 語(yǔ)言
- 2022-03-03 手寫一個(gè)angular中帶checkbox的table組件
- 2022-04-25 jQuery利用鍵盤上下鍵移動(dòng)表格內(nèi)容_jquery
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支