資料在記憶體中的儲存(詳細版)

2020-11-13 13:00:52


一、整型在記憶體中的儲存

(32位元系統)

在VS2013中,使用偵錯->視窗->記憶體,來觀察記憶體的具體情況。
在這裡插入圖片描述
讓我們通過記憶體視窗來看看整型在記憶體中的儲存,
定義一個整形變數int a= 10;


注意:可以在位址列中使用&來找到目標變數的記憶體情況。
在這裡插入圖片描述


a的地址:0x00EFF980
在這裡插入圖片描述

int型在記憶體中是以4個位元組為單位儲存的,記憶體中用16進位製表示。可以看到a的值為0a(16進位制),換算成十進位制就是10。


1、位元組序

為什麼會有大小端模式之分呢?

    這是因為在計算機系統中,我們是以位元組為單位的,每個地址單元都對應著一個位元組,一個位元組為8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於8位元的處理器,例如16位元或者32位元的處理器,由於暫存器寬度大於一個位元組,那麼必然存在著一個如果將多個位元組安排的問題。因此就導致了大端儲存模式和小端儲存模式。

(1)小端位元組序

小端(儲存)模式:
資料的低位儲存在記憶體的低地址中,而資料的高位,儲存在記憶體的高地址中。

我們輸入一個十六進位制數,來觀察一下在記憶體中的情況

int a = 0x11223344

在這裡插入圖片描述可以看到高位11儲存在記憶體的高地址中,低位44儲存在記憶體的低地址。

(2)大端位元組序

大端(儲存)模式:
資料的低位儲存在記憶體的高地址中,而資料的高位,儲存在記憶體的低地址中。

int a = 0x11223344;

以上例子在大端位元組序中的儲存是這樣的:
在這裡插入圖片描述在日常生活中,大部分的pc都是使用小端位元組序。

(3)寫一個程式判斷是否為大端位元組序

規定:返回0不是,返回1是。

#include <stdio.h>
int isBigEnd(int a) {
	int* p = &a;
	char* p2 = (char*)p;
	if (*p2 == 0x11) {
		return 1;
	}
	return 0;
}
int main()
{
	int a = 0x11223344;
	printf("%d\n", isBigEnd(a));
	system("pause");
	return 0;
}

2、原碼二補數反碼

計算機中的符號數有三種表示方法即原碼、二補數、反碼。正數的原、反、二補數都相同。
三種表示方法均有符號位和數值位兩部分,符號位都是用0表示「正」,用1表示「負」,而數值位三種表示方法各不相同。

原碼:直接將二進位制按照正負數的形式翻譯成二進位制就可以。

反碼:將原碼的符號位不變,其他位依次按位元取反就可以得到了。

二補數:反碼+1就得到二補數。

(1)二補數

對於整形來說:資料存放記憶體中其實存放的是二補數。
-10在記憶體中的儲存:
在這裡插入圖片描述在記憶體中我們可以看到在記憶體的儲存的值為 f6 ff ff ff 。

在上面已經為大家介紹了小端位元組序,因為我的計算機是小端位元組序。
所以它的值就為ff ff ff f6。

用二進位制的方式表示:
1111 1111 1111 1111 1111 1111 1111 0110
-10的原碼:
1000 0000 0000 0000 0000 0000 0000 1010
-10的反碼:
1111 1111 1111 1111 1111 1111 1111 0101
-10的二補數(反碼+1):
1111 1111 1111 1111 1111 1111 1111 0110

可以看到和上面ff ff ff f6轉換成二進位制的結果相同。

(2)二補數的意義

為什麼對於整形來說,資料存放記憶體中其實存放的是二補數呢?

   資料存放記憶體中其實存放的是二補數在計算機系統中,數值一律用二補數來表示和儲存。原因在於,使用二補數,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理(CPU只有加法器)此外,二補數與原碼相互轉換,其運算過程是相同的,不需要額外的硬體電路。

二補數存在的意義就是讓硬體實現簡單

比如:把負數按二補數表示,可以統一±為+。
例子:
1-10=1+(-10)
在這裡插入圖片描述

在這裡插入圖片描述

二、浮點型在記憶體中的儲存

1、浮點型

根據國際標準IEEE(電氣和電子工程協會),任意一個二進位制浮點數V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符號位,當s=0,V為正數;當s=1,V為負數。
M表示有效數位,大於等於1,小於2。
2^E表示指數位。
舉例來說:
十進位制的5.0,寫成二進位制是101.0,相當於1.01×2^2。 那麼,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十進位制的-5.0,寫成二進位制是-101.0,相當於-1.01×2^2。 那麼,s=1,M=1.01,E=2。

IEEE 754規定:
對於32位元的浮點數,最高的1位是符號位s,接著的8位元是指數E,剩下的23位為有效數位M。在這裡插入圖片描述

對於64位元的浮點數,最高的1位是符號位S,接著的11位是指數E,剩下的52位為有效數位M。
在這裡插入圖片描述

M佔用的位元位越多,資料精度越高。
E佔用的位元位越多,資料範圍越大。
所以在實際開發中使用double較多。

2、浮點數的比較

使用這種方式來儲存的時候,會帶來一個很大的問題,儲存的小數往往不是一個精確值,而只是一個近似值。

範例:

#include <stdio.h>
int main()
{
	float a = 11.0;
	float b = a / 3.0;
	if (b * 3.0 == a) {
		printf("相等!\n");
	} else {
		printf("不相等\n");
	}
	system("pause");
	return 0;
}

實際上11.0/3.0*3.0肯定等於11.0。
但是我們看看執行結果:
在這裡插入圖片描述所以,浮點數在記憶體中儲存的時候,很多時候是有誤差的。


正確的比較方法:

使用做差的方法,然後判斷差值是不是在允許誤差範圍內,如果在的話,就相等。

#include <stdio.h>
#define N 1e-4
int main()
{
	float a = 11.0;
	float b = a / 3.0;
	if (b * 3.0 - a < N && b * 3.0 - a > -N) {
		printf("相等, 此處不是嚴格相等, 而是允許誤差\n");
	} else {
		printf("不相等\n");
	}
	system("pause");
	return 0;
}

在這裡插入圖片描述