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