- 博客主頁:向不悔
- 本篇專欄:[C]
- 您的支持,是我的創作動力。
文章目錄
- 0、總結
- 1、整數在內存中的存儲
- 1.1 整數的二進制表示方法
- 1.2 不同整數的表示方法
- 1.3 內存中存儲的是補碼
- 2、大小端字節序和字節序判斷
- 2.1 什么是大小端
- 2.2 為什么有大小端
- 2.3 練習
- 2.3.1 練習1
- 2.3.2 練習2
- 2.3.3 練習3
- 2.3.4 練習4
- 2.3.5 練習5
- 2.3.6 練習6
- 3、浮點數在內存中的存儲
- 3.1 練習
- 3.2 浮點數的存儲
- 3.2.1 浮點數是什么
- 3.2.2 浮點數存儲的標準
- 3.2.3 浮點數的組成
- 1、符號位(S)
- 2. 指數位(E)
- 3. 尾數位(M)
- 3.3 浮點數存儲的步驟
- 3.3.1 轉換為二進制形式
- 3.3.2 規范化處理
- 3.3.3 確定各部分的值
- 3.3.4 組合成二進制序列
- 3.4 從內存中讀取浮點數
- 3.4.1 提取各部分的值
- 3.4.2 還原浮點數
- 3.5 特殊情況
- 3.5.1 浮點數存儲中特殊情況的說明
- 3.5.2 特殊情況:指數位E全為0
- 1. 指數的真實值計算
- 2. 有效數字M的處理方式
- 3. 特殊用途
- 3.5.3 特殊情況:指數位E全為1
- 1. 有效數字M全為0
- 2. 有效數字M不全為0
- 3.6 題目解析1:9 -> 0.000000
- 3.7 題目解析2:9.0 -> 1091567616
0、總結
1、整數在內存中的存儲
1.1 整數的二進制表示方法
整數的二進制表示方法有三種:原碼、反碼和補碼。這三種表示方法均包含符號位和數值位兩部分。符號位用0
表示“正”,用1
表示“負”,數值位的最高位被視作符號位,其余位為數值位。
1.2 不同整數的表示方法
- 正整數:正整數的原碼、反碼和補碼是相同的。
- 負整數:負整數的三種表示方法各不相同。
- 原碼:直接將數值按照正負數的形式翻譯成二進制得到。
- 反碼:將原碼的符號位保持不變,其余位依次按位取反。
- 補碼:反碼加
1
得到補碼。
1.3 內存中存儲的是補碼
對于整數而言,數據在內存中實際存儲的是補碼。原因如下:
- 使用補碼可以將符號位和數值域統一處理。
- 加法和減法也可以統一處理(CPU只有加法器)。
- 補碼與原碼相互轉換的運算過程相同,無需額外的硬件電路。
2、大小端字節序和字節序判斷
2.1 什么是大小端
當超過一個字節的數據在內存中存儲時,就會涉及到存儲順序的問題,根據不同的存儲順序,可分為大端字節序存儲和小端字節序存儲,具體概念如下:
- 大端存儲模式 :數據的低位字節內容保存在內存的高地址處,數據的高位字節內容保存在內存的低地址處。
- 小端存儲模式 :數據的低位字節內容保存在內存的低地址處,數據的高位字節內容保存在內存的高地址處。
如圖:
2.2 為什么有大小端
在計算機系統中,存儲和處理數據的基本單位是字節,每個地址單元對應一個字節,一個字節為 8bit 位。但在實際編程和數據處理中,除了 8bit的 char 型數據外,還存在 16bit 的 short 型、32bit 的 long型等多種數據類型(具體數據類型所占字節數會因編譯器而異)。同時,對于位數大于 8 位的處理器,如 16 位或 32位的處理器,其寄存器寬度大于一個字節,這就面臨一個如何將多個字節的數據合理安排存儲順序的問題,大小端存儲模式正是為了應對這種情況而產生的兩種不同的存儲方案。
例如:一個 16bit 的 short 型變量 x,其在內存中的起始地址為 0x0010,x 的值為 0x1122,其中 0x11為高字節,0x22 為低字節。對于大端模式,會將高字節 0x11 放在低地址 0x0010 處,低字節 0x22 放在高地址 0x0011處;小端模式則相反,將低字節 0x22 放在低地址 0x0010 處,高字節 0x11 放在高地址 0x0011處。不同的處理器架構對大小端的支持有所不同,常見的 X86 結構采用小端模式,KEIL C51 采用大端模式,很多 ARM、DSP也采用小端模式,而部分 ARM 處理器還可以通過硬件設置來選擇使用大端或小端模式。
大小端存儲模式的產生,主要是由于不同的計算機系統設計者在處理多字節數據存儲順序時有不同的思路和考量。大端模式更符合人類閱讀和書寫數字的習慣,比如我們平時寫數字時高位在左,低位在右,大端存儲在內存中存儲數據的順序與之類似,便于理解和調試。而小端模式在某些算術運算和數據處理操作中可能會更高效,例如在進行字節尋址或處理低字節優先的數據時,可以直接獲取到所需字節的數據,減少了在存儲和讀取過程中的一些額外操作和轉換步驟。因此,不同的應用場景和系統架構需求促使了大小端存儲模式的并存與演變。
2.3 練習
2.3.1 練習1
請簡述大端字節序和小端字節序的概念,并設計一個小程序判斷當前機器的字節序。(百度筆試題)
#include <stdio.h>void check_sys()
{int a = 1;char* b = (char*)&a;if (*b == 1)printf("小端\n");elseprintf("大端\n");
}void check_sys2()
{union{int i;char c;}un;un.i = 1;if (un.c == 1)printf("小端\n");elseprintf("大端\n");
}int main()
{check_sys();check_sys2();return 0;
}
運行:
小端
小端
2.3.2 練習2
#include <stdio.h>
int main()
{char a = -1;signed char b = -1;unsigned char c = -1;printf("a=%d,b=%d,c=%d", a, b, c);return 0;
}
運行:
a=-1,b=-1,c=255
總結:
char
和signed char
是有符號類型,可以直接存儲-1
。unsigned char
是無符號類型,不能存儲負數。當將-1
賦值給unsigned char
時,會通過模運算(mod)轉換為 255,即 (-1 mod 256 = 255)。- 因此,
a
和b
的值為-1
,而 c 的值為255
。 - 這里涉及的關鍵知識點是模運算(mod),用于處理無符號類型存儲負數時的轉換。
2.3.3 練習3
#include <stdio.h>
int main()
{char a = -128;printf("%u\n", a);return 0;
}
運行:
4294967168
總結:
char
是有符號類型,默認范圍是 -128 到 127。- 當使用
%u
(無符號整數格式化占位符)輸出有符號字符變量a
時,a
的值 -128 會被解釋為無符號整數。 - 在 32 位系統中,
char
類型的 -128 在內存中的二進制表示為0xFFFFFF80
(補碼形式)。當以無符號整數解釋時,其值為 4294967168。
#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}
4294967168
總結:
- 當給
char
類型變量a
賦值 128 時,超出了其表示范圍,會發生溢出。在內存中,a
的值會變成 -128(補碼形式)。
2.3.4 練習4
#include <stdio.h>
int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}
運行:
255
總結:
char
數組的符號性:char
在多數編譯器中默認為signed char
,范圍是-128到127。- 賦值邏輯:循環中
a[i] = -1 - i
,當i=255
時,表達式值為-256。由于char
是8位,-256 mod 256 = 0,此時a[255]
被賦值為0。 - 字符串終止符:
strlen
遇到第一個'\0'
(即ASCII 0)時停止計數。數組中第一個0出現在索引255處。 - 結果計算:從
a[0]
到a[254]
共255個字符,故strlen(a)
返回255。
2.3.5 練習5
#include <stdio.h>
unsigned char i = 0;
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n");}return 0;
}
運行:
死循環
總結:
unsigned char
的范圍:unsigned char
的取值范圍是 0~255(8 位無符號整數)。- 當 i = 255 時,執行 i++ 后,i 會溢出,變成 0(而不是 256,因為
unsigned char
無法存儲 256)。
- 循環條件 i <= 255 永遠成立:
- 每次 i 達到 255 后,i++ 使其變回 0,循環永遠不會終止。
#include <stdio.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);}return 0;
}
運行:
死循環
總結:
unsigned int
的范圍:unsigned int
的取值范圍是 0 到 4294967295(32 位無符號整數)。- 當 i = 0 時,執行 i-- 后,i 會 下溢,變成 4294967295(即
UINT_MAX
),而不是 -1。
- 循環條件
i >= 0
永遠成立:- 由于
unsigned int
永遠不會小于 0,循環條件始終為真。 - 當 i 遞減到 0 并繼續 i-- 時,它會變成最大值,循環永遠不會終止。
- 由于
執行流程:
- 輸出 9, 8, 7, …, 1, 0(正常部分)。
- 然后 i-- 使 i = 4294967295,繼續循環:
- 輸出 4294967295, 4294967294, …,無限循環。
2.3.6 練習6
#include <stdio.h>
int main()
{int a[4] = { 1, 2, 3, 4 };int* ptr1 = (int*)(&a + 1);int* ptr2 = (int*)((int)a + 1);printf("%x\n", ptr1[-1]);printf("%x\n", *ptr2);return 0;
}
運行:
4
崩潰
總結:
第一個輸出(4)的原因分析
&a
獲取的是整個數組的地址,類型為int (*)[4]
(指向長度為4的數組的指針)- 指針運算的單位是所指向類型的大小
解釋
&a + 1
:- 跳過了整個數組(4個int,通常16字節)
- 指向數組末尾的下一個內存位置
ptr1[-1]
等價于*(ptr1 - 1)
:- 將
ptr1
回退一個int
(4字節)的位置 - 實際指向
a[3]
,其值為4
- 將
第二個操作(崩潰)的原因分析
- 違反了指針的對齊規則
- 進行了非法的類型轉換和指針運算
解釋
(int)a
:- 將指針強制轉換為整數值(假設32位系統)
(int)a + 1
:- 簡單地將地址數值加1(如0x1000 → 0x1001)
- 不是按照int大小增加(不是加4字節)
(int *)
轉換回指針:- 新指針指向第一個
int
的中間位置(不對齊) - 在大多數架構上,
int*
必須4字節對齊
- 新指針指向第一個
3、浮點數在內存中的存儲
3.1 練習
#include <stdio.h>
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值為:%d\n", n);printf("*pFloat的值為:%f\n", *pFloat);*pFloat = 9.0;printf("num的值為:%d\n", n);printf("*pFloat的值為:%f\n", *pFloat);return 0;
}
運行:
n的值為:9
*pFloat的值為:0.000000
num的值為:1091567616
*pFloat的值為:9.000000
3.2 浮點數的存儲
3.2.1 浮點數是什么
浮點數就是帶有小數部分的數字,比如 3.14、5.0 這樣的數。在計算機里,浮點數的存儲方式和整數有所不同。
3.2.2 浮點數存儲的標準
計算機中浮點數的存儲遵循 IEEE 754 標準,這個標準規定了浮點數在內存中的表示方法。
3.2.3 浮點數的組成
一個浮點數在內存中占據一定的字節空間,以 32 位浮點數(float 類型)為例,它由三個部分組成:
1、符號位(S)
- 位置 :最高位(最左邊的那一位)。
- 作用 :表示這個浮點數是正數還是負數。
- 約定 :0 表示正數,1 表示負數。
2. 指數位(E)
- 位置 :緊跟在符號位后面的 8 位。
- 作用 :用來表示浮點數的大小范圍,相當于科學計數法中的指數部分。
- 特殊處理 :存儲時,真正的指數值要加上一個中間數(偏置值),對于 8 位指數位,這個中間數是 127。比如,如果實際指數是 3,那么存儲的時候就是 3 + 127 = 130,二進制表示為
10000010
。
3. 尾數位(M)
- 位置 :剩下的 23 位。
- 作用 :表示浮點數的有效數字,決定了浮點數的精度。
- 特殊處理 :根據 IEEE 754 標準,在存儲時默認尾數的第 1 位是 1,所以在內存里只保存后面的部分,這樣可以節省空間,提高精度。
3.3 浮點數存儲的步驟
假設我們要存儲一個十進制浮點數 5.0:
3.3.1 轉換為二進制形式
- 將 5.0 轉換為二進制是
101.0
。
3.3.2 規范化處理
- 把二進制數轉換為類似科學計數法的形式,即 1.01×2^2,這樣就滿足了尾數部分第 1 位為 1 的要求。
3.3.3 確定各部分的值
- 符號位(S) :因為 5.0 是正數,所以 S = 0。
- 指數位(E) :實際指數是 2,加上偏置值 127 后,得到 129,二進制表示為
10000001
。 - 尾數位(M) :尾數部分是 01,后面補 21 個 0 來占滿 23 位,即
01000000000000000000000
。
3.3.4 組合成二進制序列
- 按照符號位、指數位、尾數位的順序組合起來,得到
0 10000001 01000000000000000000000
。
3.4 從內存中讀取浮點數
當計算機從內存中讀取這個二進制序列并還原成浮點數時,會進行以下操作:
3.4.1 提取各部分的值
- 符號位(S) :0,表示正數。
- 指數位(E) :
10000001
,轉換為十進制是 129,減去偏置值 127,得到實際指數 2。 - 尾數位(M) :
01000000000000000000000
,在前面補上默認的 1,得到 1.01。
3.4.2 還原浮點數
- 根據提取的值,還原出浮點數為 (-1)^0 ×1.01×2^2=5.0。
3.5 特殊情況
3.5.1 浮點數存儲中特殊情況的說明
在浮點數存儲中,除了常規的數值表示外,還有一些特殊情況用于表示一些特殊的數值,比如±0和±∞。
3.5.2 特殊情況:指數位E全為0
當指數位E全為0時,浮點數的表示遵循以下規則:
1. 指數的真實值計算
- 指數E的真實值等于1減去中間數(偏置值),即對于32位浮點數,E的真實值為1 - 127 = -126;對于64位浮點數,E的真實值為1 - 1023 = -1022。
2. 有效數字M的處理方式
- 在這種情況下,有效數字M不再在前面加上默認的1,而是還原為0.xxxxxx的形式,其中xxxxxx表示M的小數部分。
3. 特殊用途
- 這種情況用于表示±0以及接近于0的很小的數字。例如,0可以表示為符號位為0,指數位全為0,尾數位全為0的二進制序列。
具體情況實例:
- 符號位(S)為0表示正數,指數位全為0,尾數位為
00100000000000000000000
,此時指數真實值為-126,有效數字M還原為0.00100000000000000000000,則浮點數為 (-1)^0 × 0.00100000000000000000000 × 2^(-126),即 0.000000117…(十進制表示為一個非常接近0的正數)。
3.5.3 特殊情況:指數位E全為1
當指數位E全為1時,浮點數的表示遵循以下規則:
1. 有效數字M全為0
- 如果有效數字M全為0,則表示±無窮大(正負取決于符號位S)。此時,符號位為0表示正無窮大,符號位為1表示負無窮大。
2. 有效數字M不全為0
- 如果有效數字M不全為0,則表示這不是一個有效的浮點數(Not a Number,NaN)。通常用于表示一些無法計算的結果,如0/0等。
具體實例:
- 符號位(S)為0,指數位全為1(二進制為
11111111
),尾數位全為0,表示正無窮大。 - 符號位(S)為1,指數位全為1,尾數位全為0,表示負無窮大。
3.6 題目解析1:9 -> 0.000000
- 整數 9 的二進制表示 :當我們將整數 9 以整型的形式存儲在內存中時,其二進制序列為
00000000 00000000 00000000 00001001
。 - 將 9 的二進制序列按浮點數形式拆分
- 符號位(S) :取二進制序列的最高位,即第 1 位,得到 S = 0,表示這是一個正數。
- 指數位(E) :接下來的 8 位(第 2 位到第 9 位)為指數位。在這里,這 8 位全為 0,即 E = 00000000。
- 尾數位(M) :剩下的 23 位(第 10 位到第 32 位)為尾數位。在這里,尾數位為 000000000000000000000000001001。
- 判斷情況并計算浮點數值
- 由于指數位 E 全為 0,因此符合 IEEE 754 標準中 E 為全 0 的特殊情況。
- 根據標準,此時浮點數的指數部分的真實值等于 1 - 127 = -126(對于 32 位浮點數,中間數是 127)。
- 同時,尾數部分不再在前面補 1,而是直接作為小數部分,即 M = 0.000000000000000000001001。
- 因此,浮點數 V = (-1)^S × M × 2^(E 真實值) = (-1)^0 × 0.000000000000000000001001 × 2^(-126) = 1.001 × 2^(-146)。
- 這個值是一個非常接近于 0 的正數,在十進制小數中通常表示為 0.000000。
3.7 題目解析2:9.0 -> 1091567616
- 浮點數 9.0 的二進制表示
- 首先,將 9.0 轉換為二進制形式為
1001.0
。為了符合浮點數的存儲規范,我們需要將其轉換為科學計數法形式,即 1.001 × 2^3。 - 這里的 1.001 是尾數部分,2^3 是指數部分。
- 首先,將 9.0 轉換為二進制形式為
- 確定各部分的值
- 符號位(S) :9.0 是正數,所以 S = 0。
- 指數位(E) :實際指數是 3。根據 IEEE 754 標準,存儲時需要將指數加上一個中間數(對于 32 位浮點數,中間數是 127),所以 E = 3 + 127 = 130,其二進制表示為
10000010
。 - 尾數位(M) :尾數部分是 001,后面再加 20 個 0 來湊滿 23 位,即 M =
00100000000000000000000
。
- 組合成二進制序列
- 按照符號位、指數位、尾數位的順序組合起來,得到的二進制序列為
0 10000010 00100000000000000000000
。
- 按照符號位、指數位、尾數位的順序組合起來,得到的二進制序列為
- 將二進制序列當作整數解析
- 這個 32 位的二進制序列被當作一個整數來解析時,其對應的十進制數值可以通過將二進制數轉換為十進制數來得到。
- 這個二進制數轉換為十進制數后為 1091567616。
完。