手繪知識點——指針運算&變量的內存分配原理

九月的最後一天,首先祝我們的祖國生日快樂,讓我們繼續砥礪前行……

來到了指針系列的第三篇,我們來說說指針的運算以及變量在內存中的存儲問題,重點在於後者。

首先看一下指針的算術運算:

int a = 1,*pa=&a,*pa1=&a;
double b=3.1415,*pb=&b;

printf("before pa++,pb++:\n");
printf("&a=%#x,pa=%#x,pa1=%#x,&b=%#x,pb=%#x\n", &a,pa,pa1,&b,pb);

pa++; pb++;
printf("after pa++,pb++:\n");
printf("&a=%#x,pa=%#x,pa1=%#x,&b=%#x,pb=%#x\n", &a, pa, pa1, &b, pb);
if (pa==pa1)
     {
	printf("pa和pa1指向同一數據\n");
     }
else
     {
	printf("pa和pa1指向不同數據\n");
     }

我們定義了整型變量a以及指向它的兩個指針pa和pa1,浮點型變量b以及指向它的指針pb,建議大家以後定義指針用這種形式,避免我上次犯的錯誤,誤將pb定義成了int *型;

看結果:

在指針++前後做對比可以發現,整型指針pa++後值增加了4,而浮點型指針pb增加了8,這正是對應數據類型所佔的內存大小,反映到圖上大概是這樣的:

因爲一個整型變量a佔據4個字節,而內存的分配基本單位爲1個字節,所以pa++後pa應前進4個字節指向如上圖所示位置,假如pa++後pa前進1個字節,那pa將把其後的四個字節當作一個整體,其中前三個字節屬於變量a,最後一個字節並不知道是個啥,所以最終的輸出也就很難保證了。最後的pa和pa1比較的是他們的值,即保存的地址,由於二者所保存的地址不一樣,所以輸出爲pa和pa1指向不同的數據。

第二部分我們說一下變量在內存中的分配問題,其實這個東西在前兩篇都已經提到了,我們這次再深入一些~

int g=0,x = 1, y = 2, z = 3;
int *m = &z;
for (int i = 0; i<20; i++) {
	printf("%d, ", *(m + i));
}
printf("\n");

int g1=0,x1 = 4,  y1= 5, z1 = 6;
int *m1 = &z1;
for (int i1 = 0; i1<20; i1++) {
	printf("%d, ", *(m1 + i1));
}
printf("\n");

int  g2=0,x2= 7, y2 = 8, z2 = 9;
int *m2 = &z2;
for (int i2 = 0; i2<20; i2++) {
	printf("%d, ", *(m2 + i2));
}
printf("\n");

printf("m=%#x,m1=%#x,m2=%#x\n", m, m1, m2);
printf("*(m2 -3) = %#x,*(m2 -2) = %d,*(m2 -1) = %d\n",*(m2 - 3), *(m2 - 2), *(m2 - 1));

看着又是好大一坨,其實都是一種形式,這一塊我做了多次試驗,不斷地添加東西,所以現在看着有些雜亂,咱們還是長話短說,從上述代碼大家應該能看到,我們連着定義了很多變量,然後使用for循環輸出,一開始的時候只有xyz三個變量,循環輸出8個值,感覺不是很明顯,之後又加到循環輸出12個值,此時可以看出一些端倪:

當然這時候還沒有寫最後一條輸出語句~大家可以先看最後的m、m1和m2,地址依次減小,從整體上符合我們之前討論過的地址的分配原則(變量入棧,後進先出,所以後定義的變量先分配地址),然後再看上邊輸出的三行數據,依然是滿足“後定義先分配”的原則,先輸出z,之後依次是y和x,只不過這中間還有些其他數據,而且多次實驗證明這個“其他數據”還是固定的-858993460,而且出現的很有規律,都在兩個連續定義的變量中間出現兩次,而這個值正是debug模式下編譯器對未初始化的變量賦的默認值~~~

並且大家可以看到,m、m1和m2之間是有關係的,兩兩之差都爲60,即60個內存單元(字節),而一個int型變量佔據4個字節,每兩個變量之間還有兩個默認值共佔8個字節,從上述代碼可以看到我們定義了四個變量x-y-z-m-i,這樣每一部分共佔15字節大小(大家可以理解成每個變量後跟兩個整型的默認值),正是每一部分起始地址之差~~

怎麼樣,是不是感覺已經亂了,這一塊真的是好說不好寫啊,我們還是直接看圖吧:

爲了方便我直接將上圖中的一小塊寫成4字節,這樣看起來比較直觀,大家可以發現每一部分的樣式是相同的,從m2開始依次存儲,其中XX代表默認值-858993460;(以下這一部分實在慚愧,大家可以直接跳到最後看總結)

這裏邊最大的亮點就是我們上圖標出的m1,由於這個值是以十進制輸出的,並且每次運行都會改變,所以我立即就想到了這是不是某個地址,因爲我們定義的這些變量在不同批次的運行時所分配的內存空間都不一樣,果不其然,十進制轉十六進制,就是下一個部分的起始地址,而大家需要注意的是這個地址是在每一部分開始前的內存空間中,基於這樣的原理我們可以假設最開始分配空間的m2的地址在其後方三個整型存儲空間大小的位置,於是我們使用上述代碼的最後一行依次輸出:

不出所料,m2-3所指向的內存空間的值正是m2的值,這裏需要說明的是(m2 - 3)是個指針,大家要分清指針的值和指針所指向數據的值的區別,即m2 - 3和 *(m2 - 3)的不同,對應於上邊的手工畫圖,前者爲0x73f91c,後者爲0x73f928

爲了更加直觀我們可以看一下上述代碼最終的輸出效果,看看每一部分輸出20次並着增加一個變量g之後是怎麼樣的結果:

不難看出,變量的輸出順序和我們想象的一樣,並且這些空間可以理解成是在一定基礎上連續的,比如倒數第二行,這是第三部分的內容,當輸出到後邊出現了“6”,而這正是第二部分開始輸出的內容,這樣三部分內容就連起來了~~~

這其中還有一個問題就是第一行的輸出並不按套路出牌,i的值20沒有正常輸出,而是顯示的11795016,再往後也是一塌糊塗,並且每次運行所得結果都在變化,由此可以推想這一塊也是系統隨機分配的~

當然這一塊內容細說起來還是挺多的,大家可以嘗試修改數據類型爲double,看看會有什麼變化,那個默認值-858993460會以怎樣的形態出現,自己動手運行一下,會有驚喜。。

大概就這些了,又是相當囉嗦,總結一下吧:

1. 指針的算術運算所移動的單位量取決於所指向的數據類型,這也體現了定義指針時的類型要和所指數據類型一致的重要性;

2. 連續定義的變量之間地址是不連續的(通常情況是這樣,如果有例外還請大家不吝賜教),中間會有一些輔助性的數據(通常爲兩個四字節的默認值);並且越先定義的變量分配的地址越靠後,這其中需要注意的是比如我們定義int a=1,b=2,c=3;   int d=4;那麼d的地址是最先分配的,其次是c、b、a~

ok,現在說一下剛剛發現的一個問題,就在剛纔我試着運行上邊定義的abcd,結果跟想象的不一樣,本以爲按照前邊的輸出,d的地址應該是靠後的,木有想到其地址是最先分配的,此刻突然想到前邊提到的第一行中的變量i沒有按套路出牌輸出20,因爲這裏根本就不是第一部分的變量i!!!!大家看一下我們定義變量的順序,按照先定義後分配的原則應該是先分配給i地址,之後是指針m,再往後是zyx,這個原則不管是在全局還是局部都是成立的,也就是說我的手工畫圖中的那個變量i不是第三部分的而是第二部分的!!!這麼重要而淺顯的一點我竟然沒有意識到。。。總之就是我們定義的這三部分正確的打開方式應該是從變量i開始,依次存儲的是i-m-z-y-x。。

好了,就這樣吧,老了老了,腦子是真的反應遲鈍,讓我緩緩,各位午安……

如果文章對您有一點點幫助,還請打賞一二,您的鼓勵將是我前進的不竭動力

 

 

公衆號爲“非著名IT表演藝術家”,比較中二的名字,就是靈光一閃,然後這個名字就冒出來了……


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章