首頁 > 軟體

C語言深入分析整形資料儲存

2022-08-12 22:00:45

資料型別

C語言中存在著資料型別,我們或多或少都見到過。

char //字元資料型別 - 1個位元組

short //短整型 - 2個位元組

int //整形 - 4個位元組

long //長整型 - 4/8個位元組

long long //更長的整形 - 8個位元組

float //單精度浮點數 - 4個位元組

double //雙精度浮點數 - 8個位元組

小思考:C語言有沒有字串型別?

C語言有字串,表示為"字串內容"的形式,但不存在字串型別。

型別存在的意義是什麼?

  • 使用這個型別開闢記憶體空間的大小(大小決定使用範圍)。
  • 如何看待記憶體空間的視角(例如指標解除參照和指標運算)。

型別的分類

整形

char
    unsigned char
    signed char
//雖然是字元型別,但是字元型別儲存的時候,儲存的字元的ascii碼值,ascii碼值是整數
short
    unsigned short [int]
    signed short [int]
int
    unsigned int
    signed int
long
    unsigned long [int]
    signed long [int]

unsigned 和 signed

  • unsigned:無符號,只有正數的資料可以存放在無符號的變數中。
  • signed:有符號,有正負的資料可以存放在有符號的變數中。

Tips:

對於short,int,long,long long資料在進行定義時,預設都為signed。而對於char型別則不確定,C語言標準沒有規定char是否有符號,取決於編譯器,所以char實際上可以歸為3類,char(不確定),signed char(有符號),unsigned char(無符號)。在vs2022中,char預設為signed char。

浮點型

float//單精度浮點數 - 4個位元組
double//雙精度浮點數 - 8個位元組

構造型別

//例:int arr[10]
陣列型別 int [10]
//陣列只要個數和元素型別發生變化,型別都會發生變化
結構體型別 struct
列舉型別 enum
聯合型別 union

指標型別

int *pi;//整形指標
char *pc;//字元指標
float* pf;//單精度浮點數指標
void* pv;//空型別指標

空型別

void 表示空型別(無型別)

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

例:

void test1()//無返回值
{}
void test2(void)//函數接收引數,引數部分加void
{}
int main()
{
	void* p = NULL;
	//void*可以存放任何型別的指標
	int a = 10;
	void* p1 = &a;//沒問題
	p1++;//err,不知道型別,無法決定跳過幾個位元組
	*p1;//err,不知道型別,無法決定解除參照的許可權
	//一般用來臨時存放地址,用的時候拿走或者強轉使用
	return 0;
}

整形在記憶體中的儲存

一個整形變數的建立需要再記憶體中開闢四個位元組,那整形在記憶體中是如何儲存的?

比如:

int a = 10;

int b = -10;

在瞭解整形在記憶體中如何儲存之前我們需要了解以下概念:

原碼、反碼、二補數

計算機中的整數有三種2進位製表示方法,即原碼、反碼、二補數。

三種表示方法均有符號位和數值位兩部分,二進位制序列的第一位為符號位,其他均為數值位,符號位數值位均由0,1組成。

原碼:

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

反碼:

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

二補數:

反碼 + 1得到二補數。

注意:

  • 正整數的原碼、反碼、二補數都相同。
  • 負整數的三種表示方式各不相同,需要通過計算得到。

樣例:

int a = 10;//整形值
//0000 0000 0000 0000 0000 0000 0000 1010 a的原、反、補
//轉化為16進位制:0X0000000a
int b = -10;//整形值
//1000 0000 0000 0000 0000 0000 0000 1010 b的原碼
//1111 1111 1111 1111 1111 1111 1111 0101 b的反碼
//1111 1111 1111 1111 1111 1111 1111 0110 b的二補數
//轉化為16進位制:0Xfffffff6

那麼對於整形而言,在記憶體中儲存的是什麼呢?

讓我們啟動偵錯,檢視記憶體:

我們可以看到對於a和b分別儲存的是二補數,這是為什麼?

在計算機系統中,數值一律用二補數來表示和儲存。原因在於,使用二補數,可以將符號位和數值域統一處理;

同時,加法和減法也可以統一處理(CPU只有加法器)此外,二補數與原碼相互轉換,其運算過程是相同的,不需要額外的硬體電路。

舉個簡單的例子,例如計算機在計算a - b的時候,會轉化成a + (-b)的形式進行計算,而這時使用原碼來進行計算,是無法計算出結果的,但使用二補數就可以計算出結果。

但是對於資料在記憶體中儲存的方式很奇怪,它是倒著儲存的,這是為什麼?讓我們瞭解一下大小端。

大小端介紹

什麼是大端小端:

大端位元組序儲存:把一個資料低位位元組處的資料存放在高地址處,把高位位元組處的資料放在低地址處

小端位元組序儲存:把一個資料低位位元組處的資料存放在低地址處,把高位位元組處的資料放在高地址處。

例如:

0x11223344

為什麼會有大端和小端:

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

例如:一個16bit 的short 型x ,在記憶體中的地址為0x0010 , x 的值為0x1122 ,那麼0x11 為高位元組, 0x22 為低位元組。對於大端模式,就將0x11 放在低地址中,即0x0010 中, 0x22 放在高地址中,即0x0011 中。小端模式,剛好相反。我們常用的X86 結構是小端模式,而KEIL C51 則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬體來選擇是大端模式還是小端模式。

其實資料在記憶體中無論是大小端儲存或者亂序儲存都可以,大小端位元組序儲存也是為了讓儲存方式變得更簡單,如果亂序儲存的話在還原資料時會更加複雜。

注:大小端儲存時以位元組為單元,16進位制的兩位為一個位元組,為一個單元,按照大小端儲存規律儲存,並不會將16進位制的每一位都倒過來儲存。例如0x123456按照小端儲存就為56 34 12 00,而不是65 43 21 00。

所以說大小端位元組序儲存,就是以位元組為單位的儲存順序。

一道筆試題

請簡述大端位元組序和小端位元組序的概念,設計一個小程式來判斷當前機器的位元組序。

思路:資料在記憶體中是通過二補數的形式儲存的,判斷大端還是小端,例如數位1,我們只需要觀察它的第一個位元組為0或1,就可以判斷位元組序。而資料型別決定了指標解除參照時看待記憶體的視角,所以我們可以用char*指標來對元素第一個位元組的內容進行解除參照。

int check_sys()
{
	int a = 1;
	//二進位制:0000 0000 0000 0000 0000 0000 0000 0001
	//十六進位制:0x00000001
	char* p = (char*)&a;//char*指標解除參照為一個位元組
	if (*p == 1)
		return 1;
	else
		return 0;
}
//簡化
//int check_sys()
//{
//	int a = 1;
//	return *(char*)&a;//1的大端或小端儲存,第一位為00或者01,取出的值正好和main函數中接收的值相同,直接返回
//}
int main()
{
	int ret = check_sys();
	if (ret = 1)
	{
		printf("小端n");
	}
	else
	{
		printf("大端n");
	}
}

char型別資料的取值範圍

char在記憶體中儲存的是字元的Ascii碼值,所以也歸於整形。但是它的取值範圍和整形不同。

char型別變數的大小為1個byte,也就是8個bit位,對於char型別,我們分signed和unsigned兩塊進行講解。

signed:

signed為有符號字元型別,二進位制序列的第一位為符號位,其他位為資料位,取值範圍為-128 ~ 127.

unsigned:

unsigned為無符號字元型別,二進位制序列全為資料位,取值範圍為0 ~ 255.

圖例:

練習

練習1

下列程式的輸出結果是什麼?

int main()
{
	char a = -1;
	//整形提升
    //1000 0000 0000 0000 0000 0000 0000 0001
	//1111 1111 1111 1111 1111 1111 1111 1110
	//1111 1111 1111 1111 1111 1111 1111 1111
    //截斷:1111 1111
    //整形提升
	//1111 1111 1111 1111 1111 1111 1111 1111 - 二補數
	//1000 0000 0000 0000 0000 0000 0000 0000
	//1000 0000 0000 0000 0000 0000 0000 0000 - 原碼
    //-1
	signed char b = -1;
	//求解過程和a相同
	unsigned char c = -1;
	//截斷:1111 1111
    //無符號字元,整形提升,高位補0
    //0000 0000 0000 0000 0000 0000 1111 1111 - 二補數==原碼
    //截斷:1111 1111
	printf("a=%d,b=%d,c=%d", a, b, c);//-1,-1,255
	//當列印a,b,c時,要整形提升
	return 0;
}

執行結果:

練習 2

下列程式的輸出結果是什麼?

int main()
{
	char a = -128;
	//1000 0000 0000 0000 0000 0000 1000 0000
	//1111 1111 1111 1111 1111 1111 0111 1111
	//1111 1111 1111 1111 1111 1111 1000 0000
	//截斷:1000 0000
	//%u - 指的是列印無符號整數
	//整形提升
    //有符號字元,補符號位
	//1111 1111 1111 1111 1111 1111 1000 0000 - 要列印原碼,而這是無符號數,所以這個就是原碼
	printf("%un", a);//4294967168
	return 0;
}

執行結果:

練習 3

下列程式的輸出結果是什麼?

int main()
{
	char a = 128;
	//0000 0000 0000 0000 0000 0000 1000 0000
	//1111 1111 1111 1111 1111 1111 0111 1111
	//1111 1111 1111 1111 1111 1111 1000 0000
	//截斷1000 0000
	//整形提升
    //有符號字元,補符號位
	//1111 1111 1111 1111 1111 1111 1000 0000 - 原碼
	printf("%un", a);//?
	return 0;
}

執行結果:

練習 4

下列程式的輸出結果是什麼?

int main()
{
	int i = -20;
	//1000 0000 0000 0000 0000 0000 0001 0100 - 原碼
	//1111 1111 1111 1111 1111 1111 1110 1011 - 反碼
	//1111 1111 1111 1111 1111 1111 1110 1100 - 二補數
	unsigned int j = 10;
	//0000 0000 0000 0000 0000 0000 0000 1010 - 二補數
	printf("%dn", i + j);//-10
	//i + j
	//1111 1111 1111 1111 1111 1111 1111 0110 - 二補數
    //列印有符號整形,轉化成原碼
	//1000 0000 0000 0000 0000 0000 0000 1001
	//1000 0000 0000 0000 0000 0000 0000 1010 - 原碼
	return 0;
}

執行結果:

練習 5

下列程式的輸出結果是什麼?

int main()
{
	unsigned int i;//恆大於0
	for (i = 9; i >= 0; i--)//死迴圈
	{
		printf("%un", i);
        //9 ~ 0 ~ 超大的值:-1的二補陣列成的迴圈
		//-1的二補數:1111 1111 1111 1111 1111 1111 1111 1111
        //放到無符號整數中,將-1的二補數直接當做原碼輸出
        //得到超大的值
        Sleep(1000);//程式停止一秒
	}
	return 0;
}

執行結果:

練習 6

下列程式的輸出結果是什麼?

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
        //char取值範圍-128 ~ 127
        //當a[i]的值小於-128時,會轉化成127並大於0的值,當a[i]=0時,
        //''的ascii碼值為0,當strlen進行計算時,計算第一個''前的字元個數
		//陣列中元素:-1 , -2 , ... ,-128 , 127, ..., 1, 0...
	}
	printf("%d", strlen(a));
	//求''前字元的個數
    //''的ascii碼值為0
}

圖解:

執行結果:

練習 7

下列程式的輸出結果是什麼?

unsigned char i = 0;//0 ~ 255
int main()
{
    int i = 0;
    //0 ~ 255為區間,迴圈進行這個區間,列印hello world
	for (i = 0; i <= 255; i++)//死迴圈
	{
		printf("hello worrldn");
	}
}

執行結果:

到此這篇關於C語言深入分析整形資料儲存的文章就介紹到這了,更多相關C語言資料儲存內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


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