日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

C語言-剖析數(shù)據(jù)是如何在內(nèi)存中存儲的(整型與浮點(diǎn)型)

作者:c鐵柱同學(xué) 更新時間: 2022-02-12 編程語言

本篇主要是深度剖析整型與浮點(diǎn)型數(shù)據(jù)在內(nèi)存中是如何儲存與讀取的。弄明白數(shù)據(jù)在內(nèi)存中的存儲方式有助于我們更好的理解代碼。

1.整型在內(nèi)存中的存儲

1.1有符號整型

在研究整型變量的存儲之前,我們要知道,整型有以下幾種類型

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可以省略,但是為什么會有char這個類型呢?,我們知道char類型叫做字符型,它表示的是用來存儲一個字符的類型,把它作為整型是因?yàn)樽址趦?nèi)存中是以ASCII值儲存的,所以我們把char型也歸為整型。每一個類型又細(xì)分出signed和unsigned兩個類型,叫做有符號型和無符號型,在我們不寫有沒有符號時,short,int,long這三個類型默認(rèn)是有符號類型,只有char類型在不寫時是有符號類型還是無符號類型是不確定的,取決于編譯器,我使用的是vs2022,這個編譯器下char默認(rèn)為signed char。

當(dāng)我們在代碼中創(chuàng)建一個變量時,我們知道要在內(nèi)存上開辟一塊空間來儲存這個變量,那么整型變量在內(nèi)存中到底是怎么存儲的呢?我們用代碼來看一下。

我們創(chuàng)建了a與b兩個整型變量,然后把3賦給a,-1賦給b,然后我們再在內(nèi)存中尋找a與b,如上圖,靠左邊的第一個行就是b的地址與b內(nèi)存儲的數(shù)值,靠右邊的第一行就是a的地址與a中存儲的元素,vs在展示內(nèi)存時,為了方便展示,顯示的是十六進(jìn)制的數(shù)據(jù),而數(shù)據(jù)在內(nèi)存中是以二進(jìn)制存儲的,一個字節(jié)的大小是八個比特位,八個比特位有2^8種數(shù)=16^2,剛好可以用兩位十六進(jìn)制數(shù)表示,所以在上圖中存放數(shù)據(jù)的位置每兩位表示一個字節(jié),因?yàn)槲覀兇娣诺氖钦驼妓膫€字節(jié),所以我把內(nèi)存顯示的寬度調(diào)為四列,方便我們觀察。

我們知道,計算機(jī)中的有符號數(shù)有三種表示方法:原碼,反碼,補(bǔ)碼。

這三種方法都有符號位和數(shù)值位,他們二進(jìn)制位的第一位就是符號位,0表示正數(shù),1表示負(fù)數(shù),其他位就是數(shù)值位。那么這三種表示方法都是怎么得到的呢?

我們直接給出結(jié)論:正數(shù)的原碼,反碼,補(bǔ)碼相同,負(fù)數(shù)的原,反,補(bǔ)碼由以下規(guī)則計算

原碼:直接把數(shù)轉(zhuǎn)換成二進(jìn)制序列

反碼:原碼的二進(jìn)制序列符號位不變,數(shù)值位按位取反

補(bǔ)碼:反碼的二進(jìn)制序列加一

現(xiàn)在我們知道了這三種表示形式都是怎么得到的,那么計算機(jī)在存儲時到底使用的是哪一種呢?我們來計算一下

首先,我們來算3

因?yàn)?是正數(shù),那么他的原,反,補(bǔ)碼都是相同的,為

(為了方便觀察,我會在后面每次展示二進(jìn)制序列時每隔八位打一個空格)

00000000 00000000 00000000 00000011--3(二進(jìn)制)
0   0    0   0    0   0    0   3   --3(十六進(jìn)制)

我們再來看-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)過一個簡單的計算我們可以發(fā)現(xiàn),我們內(nèi)存中存儲的其實(shí)是補(bǔ)碼,那么為什么內(nèi)存存儲的是補(bǔ)碼呢?

因?yàn)镃PU只有加法器,當(dāng)我們想要計算比如1-1時,計算機(jī)會模擬成1-(-1)這時候我們?nèi)绻迷a來計算,那么結(jié)果是這樣的

00000000 00000000 00000000 00000001--(1)
10000000 00000000 00000000 00000001--(-1)
10000000 00000000 00000000 00000010--(1+(-1))==(-2)?

我們發(fā)現(xiàn)這個值等于-2,是不正確的,如果我們使用補(bǔ)碼來計算

 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ǔ)碼計算的結(jié)果是正確的,使用補(bǔ)碼還有一個好處

11111111 11111111 11111111 11111111--(-1)二進(jìn)制補(bǔ)碼
10000000 00000000 00000000 00000000--對(-1)的二進(jìn)制補(bǔ)碼按位取反
10000000 00000000 00000000 00000001--又得到了(-1)的原碼

即我們可以使用相同的方法完成原碼與補(bǔ)碼之間的轉(zhuǎn)換。

使用補(bǔ)碼可以將符號位和數(shù)值位統(tǒng)一處理,同時,加法與減法也可以統(tǒng)一處理。

1.2無符號整型

以上我們就明白了整型數(shù)是怎么在內(nèi)存中存儲的,但是注意,我在定義a與b時使用的是int,我們說每個整形都有兩個細(xì)分類型,分別是有符號整型與無符號整型,在我們前面沒有寫的時候默認(rèn)為有符號整型,那么無符號整型是怎么存儲的呢?

因?yàn)閏har類型比較小,所以我們以一個char類型為例

如果這是一個有符號的char,第一位是符號位
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)

如果這是一個無符號char,八個都是數(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)

同理,其他整型的有符號型和無符號型也是可以這樣計算

1.3大小端字節(jié)序

我們知道了整型在內(nèi)存中的存儲方法后,現(xiàn)在在回到最開始的代碼,我們發(fā)現(xiàn)在存儲3時我們存儲的順序是03 00 00 00,這是為什么呢?難到是反著存的嗎?

?這與大小端字節(jié)序有關(guān),那么什么是大小端字節(jié)序呢,我們往下看

比如我現(xiàn)在要存放一個十六進(jìn)制數(shù)11 22 33 44,在內(nèi)存中現(xiàn)在有這么兩種方法

(1)

     11 22 33 44
低地址   --->    高地址   

(2)

     44 33 22 11
低地址   --->    高地址   

我們把方法(1)叫做大端字節(jié)序存儲,即:把一個數(shù)的低位字節(jié)序(低位)內(nèi)容放在高地址處,高位字節(jié)序放在低地址處。

把方法(2)叫做小段字節(jié)序存儲,即:把一個數(shù)的高位字節(jié)序(高位)內(nèi)容放在高地址處,低位字節(jié)序放在低地址處。

我們現(xiàn)在看一下當(dāng)前編譯器是采用了什么存儲模式

?我們可以看到,地址是由低到高的,而存儲順序也是由低位到高位的,所以是小段字節(jié)序存儲。

為什么會有大小端模式之分呢?這是因?yàn)樵谟嬎銠C(jī)系統(tǒng)中,我們是以字節(jié)為單位的,每個地址單元 都對應(yīng)著一個字節(jié),一個字節(jié)為8bit。但是在C語言中除了8 bit的char之外,還有16 bit的short型,32 bit的long型(要看具體的編 譯器),另外,對于位數(shù)大于8位 的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節(jié),那么必然存在著一個如何將多個字節(jié)安排的問題。因此就 導(dǎo)致了大端存儲模式和小端存儲模式。 例如:一個 16bit 的 short 型 x ,在內(nèi)存中的地址為 0x0010 , x 的值為 0x1122 ,那么 0x11 為 高字節(jié), 0x22 為低字節(jié)。對于大端 模式,就將 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式, 剛好相反。我們常用的 X86 結(jié)構(gòu)是 小端模式,而 KEIL C51 則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以 由硬件來選擇是大端模式還是小端 模式。

2. 浮點(diǎn)數(shù)在內(nèi)存中的儲存

我們先來看這樣一段代碼

?

?我們現(xiàn)在來分析一下,為什么是這樣的情況呢?

要搞明白這個結(jié)果,我們就要先弄清楚浮點(diǎn)數(shù)在計算機(jī)內(nèi)部的表示方法。

根據(jù)國際標(biāo)準(zhǔn)IEEE(電氣和電子工程協(xié)會) 754,任意一個二進(jìn)制浮點(diǎn)數(shù)V可以表示成下面的形式:

? (-1)^S * M * 2^E (-1)^s表示符號位,當(dāng)s=0,V為正數(shù);當(dāng)s=1,V為負(fù)數(shù)。

? M表示有效數(shù)字,大于等于1,小于2。

? 2^E表示指數(shù)位。

?這是什么意思呢?,我們打個比方,比如我們現(xiàn)在要表示浮點(diǎn)數(shù)6.5

6.5               --(十進(jìn)制)
110.1             --(二進(jìn)制)
1.101*2^2         --(科學(xué)計數(shù)法)
(-1)^0*1.101*2^2  --(IEEE754)

按照IEEE754的規(guī)定,S=0,M=1.101,E=2。

那么在內(nèi)存中我們是怎么來存儲這些數(shù)的呢,我們以單精度浮點(diǎn)型為例

float占4個字節(jié),32個比特位,我們?nèi)绾蝸矸峙溥@32個bit位呢?

我們以一個*代表一個bit位,把32位的內(nèi)存這樣劃分:
 *  ********  ***********************
S(1)  E(8)             M(23)

IEEE754規(guī)定:第一個比特位用來存符號位,接著八位存指數(shù)E,剩下的23位存有效數(shù)M

64位的double類型
 *  ***********  ****************************************************
S(1)   E(11)                           M(52)

對與64位的double型,最高位是符號位S,接著十一位是指數(shù)E,剩下的52位存有效數(shù)M

在規(guī)定中M是大于1小于2的數(shù),那么我們可以知道M總是1.xxxxxxxx。也就是說M的第一位永遠(yuǎn)是1,那么我們在存儲是是不是可以不存這一位,只存小數(shù)點(diǎn)后面的位,然后在使用的時候再把這個1加上即可,這樣的話我們的23位存儲M的空間就可以多存儲一位數(shù)了,以此可以提高我們浮點(diǎn)數(shù)的精度。

在存儲指數(shù)E時,首先,我們規(guī)定E是一個無符號數(shù),如果E為8位,它的取值就是0~255,若E為11位,取值就為0~2047,但是當(dāng)我們實(shí)際用來表示一個數(shù)時,(比如小于1的浮點(diǎn)數(shù)),指數(shù)位E就會是負(fù)數(shù),這時候就出現(xiàn)了問題,所以IEEE754規(guī)定,在存儲E時,E的真實(shí)值必須加上一個中間數(shù),8位加127,11位加1023,然后再存入E區(qū)

在讀取指數(shù)E時,又分為3種情況

(1)E不全為0或不全為1(正常情況)

把計算值減去127(或1023)得到真實(shí)值,再在M前面加上省去的那個1即可以還原

(2)E全為0

這時我們直接規(guī)定E的真實(shí)值為1-127或(1-1023),M前面不再加1,還原為0.xxxxx的小數(shù)

我們可以計算一下,E=0-127,這個數(shù)就是(-1)^S*M*2^(-127),可以想象的到這是一個非常小的數(shù),這是為了表示±0以及無限接近0的數(shù)。

(3)E全為1

E+127=255,E=128,我們知道2^32的值為42億多,那么2^128就是4個42億相乘,可以想象這也是一個非常大的值了,所以這時的M全為0,表示±無窮大

知道了以上這些內(nèi)容,我們再回過頭來看那個代碼

?首先是第一個printf,以整型打印n,結(jié)果是9,沒有問題

再來看第二個printf,我們用*pFloat以浮點(diǎn)數(shù)的視角來訪問這四個字節(jié),這時候電腦會認(rèn)為這里存的是浮點(diǎn)數(shù)。

00000000 00000000 00000000 00001001--9
 0 00000000 00000000000000000001001--以浮點(diǎn)數(shù)的視角來看
 S    E                M

這時這個二進(jìn)制序列就會以浮點(diǎn)數(shù)的規(guī)則被翻譯,我們發(fā)現(xiàn)它的E全為0,符號位為0,所以表示0.000000。所以打印的第二個值也沒有問題了

我們再來看第三個printf,我們先是以浮點(diǎn)數(shù)的形式在這個地址存放一個9.0,再以整型的方式打印出來。

9.0-->1001.0-->1.001*2^3
E=3+127=130-->10000010
 0 10000010 10010000000000000000000--以浮點(diǎn)數(shù)的方式存放9.0
 S    E              M

我們再把這個數(shù)以整型的讀取方式讀出來的值就是1091567616

再看第四個printf,以浮點(diǎn)數(shù)的方式讀取剛剛存貯的9.0,結(jié)果自然也是9.0

以上就是本篇的全部內(nèi)容,如果大家覺得我的文章對你有幫助,希望能夠給我點(diǎn)贊支持一下,鐵柱在這里謝謝大家了!!

原文鏈接:https://blog.csdn.net/qq_45967533/article/details/122860682

欄目分類
最近更新