首頁 > 軟體

C語言中資料在記憶體如何儲存

2021-12-08 19:01:03

資料型別

常見的資料型別

常見的資料型別 位元組
char 字元資料型別 1
short 短整型 2
int 整形 4
long 長整型 4
long long 更長的整形 8
float 浮點型 4
double 雙精度浮點型 8

注意:

  • C語言裡面沒有字串型別
  • 關於int和long的大小:C語言只是規定了:sizeof(long)>=sizeof(int)
  • 布林型別(_Bool)(C99引入)專門用來表示真假,但是在C語言中不需要布林型別也可以表示真假
#include<stdbool.h>
int main()
{
    _Bool flag = false;
    _Bool flag2 = true;
    if(flag)
    {
        printf("hahan");
	}
    if(flag2)
    {
        printf("hehen");
	}
return 0;
}//只列印了hehe

型別的基本歸類

整形

char也屬於整形(每一個字元在儲存的時候是儲存他所對應的ascll值,ascll是整數)

char unsigned char signed char
short unsigned short signed short
int unsigned int signed int
long unsigned long signed long

有符號數和無符號數

有符號數
int main()
{
	int a = 10;
    int a = -10;
	return 0;
}//a是一個有符號數,它可以儲存正負整數

//int ===> signed int
//short ===> signed short
//long ===> signed long

無符號數

有一些變數只有正數由意義,例如年齡,價格。定義這些變數的時候就可以用無符號數定義 ,無符號數只能儲存正數。

int main()
{
    unsigned int a = 10;
    //無符號變數只能儲存正數
    a = -10;
    //即使這裡輸入了一個負數,它也會把這個負數轉化成一個正數(不是簡單的去掉符號,這是關於二進位制的計算)
    return 0;
}

是否char 等於signed char呢?

答案:取決於編譯器

  • 我們會發現這樣一件事:
    int 就是 signed int
    short 就是 signed short
    long 就是 signed long
  • char 等於signed char還是unsigned char 取決於編譯器,不同的編譯器可能是不同的結果,常見的編譯器下是等於signed char
對於有符號數位和無符號數位的列印

列印無符號數應該用%u

%u和%d列印的解讀方式不同:

  • 使用%d 時,會認為這是一個有符號數,列印的時候會認為二進位制中第一位是符號位;
  • 使用%u時,會認為這是一個無符號資料,會認為整個二進位制序列都是有效位。
#include<stdio.h>
int main()
{
    unsigned int a = 10;
    printf("%u",a);//正確的形式
    
    //如果儲存了一個-10進去會怎麼樣
    a = -10;
    printf("%u",a);
    //會列印4294967286,而這個資料不是亂數
    return 0;
}

為什麼無符號整形儲存 -10 的時候會列印出來4284967286(並不是亂數)?

%u在解讀的時候認為此時a仍然儲存的是正數,解讀了a的二補數。在本章後面介紹原反補嗎的時候在詳細解釋細節。

浮點型

浮點型 大小
float 4
double 8

構造型別(自定義型別)

構造型別
陣列 陣列名去掉後剩下的就是陣列的型別
結構體 struct
列舉型別 enum
聯合(聯合體)型別 union

指標型別

指標型別
char* pc
int * pi
float* pf
void* pv

空型別

void表示空型別(無型別)

通常應用於函數的返回型別,函數的引數,指標型別

整形在記憶體中的儲存

int a = 10;
int b = -10;

然後我們觀察a、b在記憶體中的儲存

資料在記憶體裡面是以二進位制儲存的,但是編譯器是以十六進位制展現給我們看的:

a在記憶體中的值是 :0a 00 00 00

b在記憶體中的值是: f6 ff ff ff

為什麼是這樣的值呢?下面介紹整數的原碼、反碼、二補數。

原碼,反碼,二補數

整數二進位制有3種表示形式,而記憶體中儲存的是二進位制的二補數

例如 1 的原碼:

00000000 00000000 00000000 00000001

正整數

正整數的原碼、反碼和二補數相同

負整數

  • 原碼:按照一個數的正負直接寫出來的二進位制就是原碼
  • 反碼:符號位不變,其他位按位元取反 (並不是按位元取反)
  • 二補數:反碼的二進位制序列加1

從原碼轉換成二補數:先取反再加一。

從二補數轉換成原碼:可以先減一再取反,也可以先取反再加一(和原碼轉換成二補數的過程相同)。

型別 資料:15 資料:-15
原碼 00000000 00000000 00000000 00001111 10000000 00000000 00000000 00001111
反碼 00000000 00000000 00000000 00001111 11111111 11111111 11111111 11110000
二補數 00000000 00000000 00000000 00001111 11111111 11111111 11111111 11110001

解釋為什麼%u列印-10;會出現4294967286

用上面的方法我們就可以計算出-10的二補數:

資料 二補數
10 11111111 11111111 11111111 11110110

回到最開始使用%u列印-10會列印成4294967286,是因為在使用%u的時候,不會解讀符號位,會將整個32位元二進位制都當作有效位,讀出一個資料,而這個資料就是4294967286。

同時回到剛才a,b的值

int main()
{
int a = 10;
int b = -10;
    //a: 00000000 00000000 00000000 00001010
    //b: 11111111 11111111 11111111 11110110
}
//四個二進位制位轉換成一個十六進位制位
//a: 00 00 00 0a
//b: ff ff ff f6

為什麼和在記憶體介面看到的不同呢?

記憶體介面和我們計算出來的順序是相反的:

資料 計算結果 記憶體
10 00 00 00 0a 0a 00 00 00
-10 ff ff ff f6 f6 ff ff ff

為什麼會倒著存進去?

這就和位元組序有關,下面我們來了解位元組序

大小端位元組序

當儲存的內容超過一個位元組的時候,儲存的時候就有順序

(一個char型別的資料是沒有位元組序。char型別的資料只有一個位元組,沒有順序)

機器有兩種對位元組的儲存順序:

  • 大端位元組序儲存:
    低位元組資料存放在高地址處,高位元組資料存放在低地址處
  • 小端位元組序儲存
    低位元組資料存放在低地址處,高位元組資料存放在高地址處

我們用下面這個例子來解釋:

int main()
{
    int a = 0x11223344;//0x開頭說明是十六進位制數位
    //再記憶體介面看到:44 33 22 11
   
    return 0;
}

而我在觀察記憶體的時候發現我的機器是按照方式2進行儲存的,所以我的機器是採用的小端位元組序。

那麼有什麼方法可以快速判斷自己當前使用的機器屬於哪一種位元組序呢?

設計一個小程式判斷當前機器屬於哪種位元組序

#include<stdio.h>
int main()
{
    int a = 1;
    //a的十六進位制是 00 00 00 01
    //如果是大端那麼記憶體中為:00 00 00 01
    //如果是小端那麼記憶體中為:01 00 00 00
    //只需要判斷第一個位元組的內容是不是1
    char*pc = (char*)&a;
    //強制型別轉換擷取了a的第一個位元組
    if(*pc)//也可以是:if(*(char*)&a)
    {
        printf("小端");
	}
    else
    {
        printf("大端");
	}
        
	return 0;
}

儲存時資料發生截斷以及整型提升

例題1

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("%d %d %d ",a,b,c);//會列印出-1 -1 255
    return 0;
}

解釋:

  • 第一步:
    這裡我們將三個相同的整數 -1 分別存進了兩種型別的變數,(在我所使用的VS2019編譯器下char和signed char等價),而這兩種型別又都屬於char型別
  • 第二步:
    char 型別的變數只能儲存一個位元組(8個位元位)大小的資料,但是 -1 是整形(包含32個位元位),
    這裡就需要發生資料截斷: -1 的二進位制二補數是11111111 11111111 11111111 11111111
    截斷:將二補數的最後八位賦值給變數a、b、c。
  • 第三步:
    這裡需要將char型別的資料以%d的方式列印,但是%d只能解讀整數資料(整數有四個位元組),而char型別的三個變數都只有一個位元組,所以這裡會發生整型提升:
    整形提升:對於有符號資料,高位補符號位,對於無符號資料:高位補0
資料(變數) 整形提升前 整形提升後(二補數) 原碼代表的資料
a/b 11111111 11111111 11111111 11111111 11111111 -1
c 11111111 00000000 00000000 00000000 11111111 255

%d列印的時候會認為這就是要列印資料的二補數,按照列印正常整數的形式列印這三個變數

例題2

#include<stdio.h>
int main()
{
    
    char a = -128;
    printf("%u",a);//會列印4294967168
    return 0;
}

解釋:

  • 第一步:
    觀察資料: -128 是一個整數,二進位制有32位元 : 但是接受這個資料的變數是一個char型別的變數(只能接受8個位元位)
  • 第二步:
    資料的截斷:
資料 二進位制(二補數) 擷取(a儲存的部分)
-128 11111111 11111111 11111111 10000000 10000000

聯想:這裡如果是a = 128,那麼階段後的值仍然是10000000

  • 第三步:
    列印整數(四個位元組),所以這裡需要發生整形提升(a是有符號char,高位補符號位)
資料(變數) 整形提升前 整形提升後(二補數)
a 10000000 11111111 11111111 11111111 10000000
  • 第四步:
    %u的形式列印這個變數,因為%u應該列印的是無符號整數,且列印的時候認為整個32位元全為有效位,
    就會列印出4294967168

浮點型在記憶體中的儲存

常見的浮點數 例子
字面浮點數 3.14159
科學計數法的表示形式 1E10(1乘以10的10次方)

注意:

  • 在浮點型資料後面加了f是float型別,不加則預設是double型別
  • %f和%lf預設小數點後六位

浮點型和整形在記憶體中的儲存方式不同

我們通過下面這個例題來探究浮點型和整形在記憶體中的儲存方式有什麼不同

#include<stdio.h>
int main()
{

	int n = 9;
	float* pf = (float*)&n;
    //第一組
	printf("n = %dn", n);    //列印出:9
	printf("*pf = %fn", *pf);//列印出:0.000000
	//第二組
	*pf = 9.0;
	printf("n = %dn", n);	  //列印出:1091567616
	printf("*pf = %fn", *pf);//列印出:9.000000

	return 0;
}

為什麼會出現這樣的情況?

回答這個問題前,先來了解浮點型的二進位制儲存形式:

國際標準電氣電子工程師學會(IEEE):任何一個二進位制浮點數V都可以表示成下面的形式

  • 表示形式:(-1)^S * M * 2^E
  • (-1)^S表示符號位,當S為0 時表示正數,當S為1時,表示負數
  • M表示有效數位,大於等於1,小於等於2
  • 2^E表示指數位
(5.5)十進位制 (5.5)二進位制
5.5 101.1
5.5*(10^1) 1.011*(2^2)
十進位制浮點數:5.5
轉化成二進位制:101.1
可以寫成:(-1)^0 * 1.011 * (2^2)
S = 0
M = 1.011
E = 2
只需要儲存SME三個值
  • IEEE浮點數標準定義了兩種基本的格式:以四個位元組表示的的單精度格式和八個位元組表示的雙精度格式

單精度浮點數儲存格式(32位元)

第一位 接著8位 剩下23位
符號位 指數位 有效位

雙精度浮點數儲存格式(64位元)

第一位 接著11位 剩下52位
符號位 指數位 有效位

IEEE754對於有效數位M和指數E有一些特別的規定:

  • 關於M:
    1.我們已經知道1<=M<2,也即是說M一定可以寫成這樣的形式:1.xxxxxx
    其中xxxxxx表示小數點後面的部分
    2.在計算機內部儲存M時,由於第一位總是1,所以把這個1省略,只儲存後面的小數部分,這樣可以節約一位有效數位,這樣的話儲存的資料精度就可以提升一位:例如在單精度浮點型儲存格式中,最後23位作為有效位,但是儲存在計算機的資料精度是24位元
  • 關於E:
    計算機會認為這是一個無符號數,但是十幾行會存在很多E取負數的情況,所以IEEE754規定:存入記憶體時,E的真實值必須在加上一個中間數 ,對於單精度,這個中間數(也叫做偏移量)是127,對於雙精度,這個中間數是1023
    例如:對於十進位制的數位0.5,它的二進位制是0.1,E= -1;那麼我們就需要把-1在加上127得到126後,將126儲存在指數位

拿出這些儲存的資料(三種情亂)

情況一:E不全為0或不為全1

  • E: 指數位值減去127(1023)得到真實值
  • M: 有效位的數值前面加上1.

情況二:E全為0

  • E:
    真實的E是一個十分小的數位,接近0;
    這時候不用計算,E直接就等於1-127(1-1023)就是它的真實值
  • M:
    M不再在前面加上1,而是還原成0.XXXXXX的小數,這樣做是為了表示正負0,以及接近於0的很小的數位。

情況三:E為全1

  • 真實的E是一個十分大的數位,代表正負無窮大的數位

解釋上面的例子

  • 第一組為什麼以%f列印整數9,會列印出0.000000?

原因:

此時E為全0,是一個會被判定成一個十分小的資料,所以列印0.000000

  • 為什麼第二組中以%d的形式列印*pf時,會列印出1091567616?

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com