第十六講:數據在內存中的存儲
- 1.整數在內存中的存儲
- 1.1存儲方式
- 1.2大小端字節序
- 1.3大小端字節序排序規則
- 1.4為什么要有大小端
- 1.5練習
- 1.5.1練習1
- 1.5.2練習2
- 1.5.3練習3
- 1.5.4練習4
- 1.5.5練習5
- 1.5.6練習6
- 1.5.7練習7
- 2.浮點數在內存中的存儲
- 2.1練習
- 2.2浮點數的存儲
- 2.3浮點數的存儲過程
- 2.3.1符號位的存儲
- 2.3.2對于有效數字M的存儲
- 2.3.3對于指數E的存儲
- 2.3.3.1E不全為0或不全為1
- 2.3.3.2E全為0
- 2.3.3.3E全為1
- 2.4題目解析
這一講分別介紹了整數和浮點數在內存中的存儲方式,以及一些題目的解析
1.整數在內存中的存儲
1.1存儲方式
數據在內存中的存儲都是以其二進制位來表示的,而整數的二進制位的表示方法有三種:原碼、反碼、補碼,在內存中,存儲的是正數的補碼
正數的原碼、反碼、補碼相同
負數的三種表示方式各不相同
那么為什么整數存儲的是補碼呢?
1.CPU只有加法器,使用補碼能夠將字符位和數值位統一處理
2.原碼和補碼進行轉換的過程是相同的,不需要額外的硬件電路便可以實現
1.2大小端字節序
當我們對于一個整數變量進行內存監視時,常常會觀察到整數的存放順序和我們創建的變量值順序是不同的,例如:
當我們創建了一個a變量時,它在內存中的存儲為(VS編譯器):
可以看見,它是倒著存的,不是正著存的,這就涉及到了整數存儲順序的兩種方式:大端存儲和小端存儲
1.3大小端字節序排序規則
大端排序方式:
低位的字節放在高地址,高位的字節放在低地址
小端排序方式:
低位的字節放在低地址,高位的字節放在高地址
我們畫圖來解析:
1.4為什么要有大小端
我們常?的 X86 結構是?端模式,?KEIL C51 則為?端模式。很多的ARM,DSP都為?端模式。有些ARM處理器還可以由硬件來選擇是?端模式還是?端模式。
但是為什么要有著大小端的存在呢?
1.C語言有著多種類型的數(float、 int、char),對于字節安排的問題,必然要被得到處理
2.不同的硬件設計可能導致不同的存儲方式,硬件的設計使用某種存儲方式可能會優化性能或簡化設計
3.不同的存儲方式可能對性能有不同的影響
1.5練習
總結:
1.整數在運算時(整型提升、加法、減法)都是補碼在進行運算
2.對于整數的打印,分為兩種情況:1.打印有符號整數,如果需要整形提升,那么補的是符號位,將補碼轉換成原碼,進行計算之后將得出值進行打印
2.打印無符號整數,如果需要整形提升,如果最高位為1,補1,為0,補0,和有符號整數相同,但是計算結果時看的是補碼,因為無符號整數的原碼、反碼、補碼相同
1.5.1練習1
//設計?個?程序來判斷當前機器的字節序。
//
//設計思路:
//創建一個a變量,賦值為1,其在內存中的存儲應該為00 00 00 01
//①如果為小端存儲:存儲方式應該為01 00 00 00
//②如果為大端存儲:存儲方式為00 00 00 01
//分別拿出它們首個字節,如果值為1,就是小端存儲,如果值位0,就為大端存儲
//
//代碼1:
int main2()
{int a = 1;if (*((char*)&a)) //注意:這里為&a,因為只能將地址強轉成(char*)類型的指針,否則可能會出現越界訪問printf("小端存儲\n");elseprintf("大端存儲\n");return 0;
}//代碼2:
int DefA()
{int a = 1;return *((char*)&a);
}int main()
{int ret = DefA();if (ret)printf("小端存儲\n");elseprintf("大端存儲\n");return 0;
}
1.5.2練習2
//1.5.2練習2
int main()
{char a = -1;//對于char類型的變量,它可能時signed char類型,也可能是unsigned char類型,具體取決于編譯器,這里是有符號類型//char類型為一個字節,此時,-1的2進制表示為10000001,它的補碼為:11111111//因為-1為整形,所以它的補碼結果為:11111111111111111111111111111111,而a為char類型,char類型只能存儲11111111,所以對于a://補碼:11111111,由于要進行打印,發生整形提升,結果為11111111111111111111111111111111//原碼:10000000000000000000000000000001//所以結果為-1signed char b = -1;//char類型在此編譯器下就是有符號類型的,所以對于有符號類型的char分析和上面一樣//所以結果為-1unsigned char c = -1;//對于無符號類型,仍為1個字節,所以a還是11111111//整型提升結果為00000000000000000000000011111111,因為對于無符號整形,整形提升加0//此時符號位為0,所以原碼和補碼相同,計算的結果為255printf("a=%d,b=%d,c=%d", a, b, c);//以%d形式打印,表示打印有符號整數return 0;
}
1.5.3練習3
//1.5.3練習3
int main()
{char a = -128;//-128,原碼為10000000000000000000000001000000,反碼為11111111111111111111111110111111,補碼為11111111111111111111111111000000//所以a里存的是11000000//要打印的是無符號整形,進行整形提升,結果為11111111111111111111111111000000//所以結果為4294967168printf("%u\n", a);return 0;
}
1.5.4練習4
//1.5.4練習4
int main()
{char a = 128;//對于128,它的原碼為00000000000000000000000010000000//反碼:01111111111111111111111101111111//補碼:01111111111111111111111110000000//存儲到a里,結果為10000000//打印無符號整形,整形提升//補碼:11111111111111111111111110000000printf("%u\n", a);return 0;
}
但是,看練習4,當我們要將128這個值存到char類型中時,a為10000000,這顯然就是-128呀!這是因為char類型的取值范圍為-128 - 127,128根本存不下,這時存儲遵循一個規律:
所以我們可以將他們看成一個循環,對于其他類型的整數(float、int)也是如此
1.5.5練習5
//1.5.5練習5
#include <string.h>int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;//strlen是求字符串長度的函數,遇到\0會停止//對于一個char類型的數組,里面放的元素為char類型//而我們已經了解到了,char類型的取值范圍為-128 - 127//所以a數組中放的值只能為:-1 -2 -3 ... -127 -128 127 126 ... 2 1 0這些ASCII碼值對應的字符//遇到\0停止,所以結果為255}printf("%zd", strlen(a));return 0;
}
1.5.6練習6
//1.5.6練習6
unsigned char i = 0;int main()
{for (i = 0; i <= 255; i++){//無符號char類型的取值范圍為0-255,所以會一直滿足循環條件,會一直循環進行打印printf("hello world\n");}return 0;
}
#include <windows.h>int main()
{unsigned int i;for (i = 9; i >= 0; i--){//對于無符號int類型,他所有的位都會被當成數值位,所以它不會出現負數的情況//所以它會一直滿足條件,一直進行打印printf("%u\n", i);Sleep(100);}return 0;
}
1.5.7練習7
//1.5.7練習7
//X86環境 ?端字節序
int main()
{int a[4] = { 1, 2, 3, 4 };int* ptr1 = (int*)(&a + 1);//&a取出的是整個數組的地址,+1表示緊挨著數組的那塊地址,將其強轉成int*類型的指針賦給ptr1//*(ptr-1)得到的就是4int* ptr2 = (int*)((int)a + 1);//a表示首元素的地址,將其轉換成int類型,表示的是一個數!,+1表示地址加1,直接+1就可以了//但是要注意:每一個字節都有一個指針,+1表示的是向后偏移一個字節//對于a,在內存中的存儲為0x 01 00 00 00 02 00 00 00 ...(因為為小端存儲),向后偏移一個字節,就變成了://00 00 00 02 00 00 00//對他解引用,訪問4個字節,所以找到了00 00 00 02,因為為小端存儲,所以結果為02000000printf("%x,%x", ptr1[-1], *ptr2);return 0;
}
2.浮點數在內存中的存儲
2.1練習
浮點數的存儲和整形的存儲是不一樣的,下面我們就通過一個練習來直觀地感受一下:
//2.1練習
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值為:%d\n", n);//9printf("*pFloat的值為:%f\n", *pFloat);//0.000000*pFloat = 9.0;printf("num的值為:%d\n", n);//1091567616printf("*pFloat的值為:%f\n", *pFloat);//9.000000return 0;
}
2.2浮點數的存儲
既然知道了整形和浮點型的不同,那么浮點數是怎么存儲的呢?
根據國際標準IEEE(電氣和電子工程協會) 754,任意?個?進制浮點數V可以表示成下?的形式:
我們通過舉例來說:
//2.2浮點數的存儲
int main11()
{float a = 5.5;//我們來探討5.5在內存中的存儲形式://5的二進制表示為101.1,寫成科學計數法的形式為1.011 * 10^2//所以符號位S為0(因為為整數)//指數位E = 2//數值位為M = 1.011return 0;
}
如果沒有看懂,我們通過圖像來直觀感受:
我們可以簡單理解S、E、M這三個值如上
IEEE 745規定:
1.對于32位的浮點數,最高的一位存儲的是符號位,接著八位存儲指數E,剩下32位存儲有效數字M
2.對于64位的浮點數,最高的一位存儲的是符號位,接著十一位存儲指數E,剩下52位存儲有效數字M
2.3浮點數的存儲過程
2.3.1符號位的存儲
符號位的存儲只占據一個字節,很簡單,是正數就是0,是負數就是1
2.3.2對于有效數字M的存儲
其實M的取值范圍為1<=M<2,也就是說,M總是可以表示成1…的形式,所以IEEE 754規定,在計算機保存M時,只保存小數點后邊的部分,前邊的1舍去,等到讀取的時候,再將1加上去,這樣就節省了一位有效數字,使得精度更高,比如:1.01在進行存儲時,只存儲01,讀取時再將1加上;0.10可以表示成1.0 * 10的負一次冪,所以說它在存儲時存儲0就可以了,需要注意的是:它要在后邊補0,也就是說對于1.1,在存儲時存儲的是01000000000000000000000
2.3.3對于指數E的存儲
對于指數的存儲比較復雜,分為三種情況討論:
2.3.3.1E不全為0或不全為1
因為E的值可能為負數,為了將負數表示出來,我們需要將E的值加上127(在32位機器上,偏移值為127,在64位機器上,偏移值為1023),再將其轉換成二進制存儲即可,這樣即可以通過比較指數的大小來判斷兩個浮點數的大小關系,同時也可以方便地進行加減乘除等計算操作
這種情況為正常情況,比如0.5的二進制表示形式為0.1,也就是1.0 * 10的負一次冪,在存儲數值位時要將數值位的一忽略,所以存儲時存儲的就是0,補齊23位,也就是00000000000000000000000,指數位值為-1,加上127為126,二進制表示為01111110,符號位為0,所以0.5的二進制表示為:
0 01111110 00000000000000000000000
2.3.3.2E全為0
當E全為0時,指數位的值為1-127(它是規定好的),而且此時數值位在進行復原時,補的不是1了,而是0,此時表示的是一個無限接近于0的一個小數
2.3.3.3E全為1
這時表示的是一個無窮大的數
2.4題目解析
//2.4題目解析
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值為:%d\n", n);//n本來就是一個int類型的數,進行打印,結果為9printf("*pFloat的值為:%f\n", *pFloat);//對于9://原碼:00000000000000000000000000001001//對于一個float類型的數,因為要解引用,拿到的是原碼://符號位:0 - 正數//數值位:00000000000000000001001 - 0.00000000000000000001001//指數位:00000000 - 原碼為00000000 - 1-127 = -126//所以值為0.00000000000000000001001 * 10的-126次冪//他表示0.0000000000000000000...1001是一個很小的數//盡管要拿出來,拿出的也只是0.000000,所以結果為0.000000*pFloat = 9.0;//9的二進制表示1001.0 - 1.001 * 10 ^ 3//符號位:0 - 正數//數值位:1.001 - 00100000000000000000000 - 注意:要在后邊補0//指數位:3 + 127 = 130 - 10000010//全部 —— 0 10000010 00100000000000000000000 - 1,091,567,616printf("num的值為:%d\n", n);全部 —— 0 10000010 00100000000000000000000 - 1,091,567,616printf("*pFloat的值為:%f\n", *pFloat);//直接打印出9.000000即可return 0;
}