內(nèi)存地址
如果您在計算機硬件的層面上理解了內(nèi)存地址的原理,前面的討論就會變得更加清晰了。您若還沒有閱讀過位和字節(jié),那么現(xiàn)在應(yīng)該去讀一遍這篇文章,它會幫您弄清位、字節(jié)和字的概念。
所有計算機都配有內(nèi)存,也稱RAM(隨機存取存儲器)。比如您的計算機現(xiàn)在可能配有16、32或64兆字節(jié)的RAM。RAM用于存儲計算機正在執(zhí)行的程序以及程序使用的數(shù)據(jù)(即程序的變量和數(shù)據(jù)結(jié)構(gòu))。內(nèi)存可以看作是一個簡單的字節(jié)數(shù)組。在這個數(shù)組中,每個內(nèi)存單元都有自己的地址:第一個字節(jié)的地址是0,后面依次是1、2、3,等等。內(nèi)存地址相當(dāng)于普通數(shù)組的下標(biāo)。計算機可以隨時訪問內(nèi)存的任何位置(所以稱為“隨機存取存儲器”)。根據(jù)需要,多個字節(jié)可以組合起來構(gòu)成較大的變量、數(shù)組和結(jié)構(gòu)體。例如,一個浮點型變量占用4個連續(xù)字節(jié)的內(nèi)存空間。您可以像下面這樣在程序中聲明一個全局變量:
float f;
上面這條語句的意思是說:“聲明一個名為f的可以保存一個浮點值的內(nèi)存位置?!背绦驁?zhí)行的時候,計算機就會在內(nèi)存中某個位置為變量f預(yù)留空間。這個位置在內(nèi)存空間中有確定的地址,如下圖所示:
?
變量f在內(nèi)存某處占用四個字節(jié)的空間。此位置有確定的地址,本例中是248,440。
您認(rèn)為的變量f在計算機看來就是一個具體的內(nèi)存地址(如248,440)。因此,當(dāng)您寫下這樣的語句時:
f=3.14;
?
編譯器可能把它翻譯成:“將數(shù)值3.14裝入到內(nèi)存地址是248,440的位置?!庇嬎銠C總是通過操作地址和操作地址的值來使用內(nèi)存的。
另外,計算機的這種使用內(nèi)存的方式還會帶來一些“副作用”。例如,您的程序包含了下面的代碼:
int i, s[4], t[4], u=0; for (i=0; i<=4; i++) {s[i] = i;t[i] =i;} printf("s:tn"); for (i=0; i<=4; i++) printf("%d:%dn", s[i], t[i]); printf("u=%dn", u);
?
您很可能會看到這樣的程序輸出:
s:t 1:5 2:2 3:3 4:4 5:5 u=5
?
t[0]和u的值為什么不對?仔細(xì)觀察代碼就會發(fā)現(xiàn),兩個for循環(huán)在訪問數(shù)組時都越界了一個元素。在內(nèi)存中,兩個數(shù)組是相鄰存儲的,如下圖所示:
?
|
?
因此,當(dāng)向s[4]這個并不存在的數(shù)組元素寫數(shù)據(jù)時,實際上覆蓋了t[0],因為它正處于s[4]的位置上。當(dāng)向t[4]寫數(shù)據(jù)時,實際上就覆蓋了u。對于計算機來說,s[4]只是一個可以寫數(shù)據(jù)的內(nèi)存位置而已。您會發(fā)現(xiàn)盡管計算機執(zhí)行了程序,但程序卻是不正確或不正常的。這個程序在運行時損壞了t 數(shù)組。執(zhí)行下面的語句將會產(chǎn)生更加嚴(yán)重的后果:
s[1000000]=5;
?
s[1000000] 這個位置很可能在程序的內(nèi)存空間之外。也就是說,您向不屬于您程序的內(nèi)存空間寫入數(shù)據(jù)。在具備存儲空間保護的系統(tǒng)上(如UNIX和Windows 98/NT),這樣的語句會使系統(tǒng)終止程序的執(zhí)行。而在其他系統(tǒng)(如Windows 3.1和早期的Mac)會聽任程序為所欲為。結(jié)果是另一個程序的代碼或變量被破壞了。這種侵犯的后果小到不產(chǎn)生任何影響,大到導(dǎo)致徹底的系統(tǒng)崩潰。在內(nèi)存中,變量i、s、t、u具有前后相鄰的確定地址。因此,如果您對一個變量越界寫入,計算機會照您說的做,但將破壞另一處內(nèi)存位置的數(shù)據(jù)。
因為C和C++在訪問數(shù)組元素時不做任何形式的邊界檢查,所以您作為一名程序員自己一定要嚴(yán)加注意數(shù)組邊界,不要超越。對超越數(shù)組邊界內(nèi)容的無意讀寫總是會導(dǎo)致程序出現(xiàn)問題。
下面的代碼是另一個例子:
#includeint main() {int i,j;int *p; /* 指向整數(shù)的指針 */ printf("%d %d n", p, &i);p = &i;printf("%d %d n", p, &i);return 0;}
?
這段代碼告訴編譯器打印p保存的地址和i占用的地址。變量p一開始是一個隨意的數(shù)值或0。i的地址一般是一個很大的數(shù)字。例如,運行這段代碼后得到的輸出是:
0 2147478276 2147478276 2147478276
?
可知i的地址是2147478276。p=&i;這條語句執(zhí)行之后,就保存了i的地址。再試試下面的代碼:
#includevoid main() {int *p; /* 指向整數(shù)的指針 */ printf("%d n",*p);}
?
這段代碼告訴編譯器打印p指向的值。然而p尚未初始化,它保存的地址是0或一個隨機地址。多數(shù)情況下這將引發(fā)一個段錯誤(或某些其他運行時錯誤),表明您使用了一個指向無效內(nèi)存空間的指針。段錯誤幾乎總是由未初始化的指針或錯誤的內(nèi)存地址導(dǎo)致的。
通過以上的介紹,現(xiàn)在我們可以從新的角度來理解指針了。請看下面的例子:
#include
int main()
{int i;int *p; /*
p = &i;*p=5;printf("%d %d
?
程序的運行過程是這樣的:
?
變量i占4字節(jié)的內(nèi)存。指針p也占4字節(jié)(在當(dāng)今使用的多數(shù)計算機上,一個指針占4字節(jié)內(nèi)存?,F(xiàn)在大部分CPU的內(nèi)存地址都是32位的,盡管64位尋址已漸成趨勢)。i所代表的內(nèi)存位置有一個確定的地址,本例中是248,440。執(zhí)行過p=&i;后,指針p也將保存同樣的地址。因此變量*p和i是等價的。
指針p原樣保存著i的地址。當(dāng)執(zhí)行如下的語句時:
printf("%d", p);
?
?
程序就會打印變量i的實際內(nèi)存地址。
評論
查看更多