畫(huà)外音:今天是個(gè)大晴天,溫暖的陽(yáng)光透過(guò)窗子照進(jìn)了這間寬敞的辦公室,辦公室里三三兩兩的人們正在各自的計(jì)算機(jī)前努力工作,一切都顯得那么的安靜、祥和、有條不紊
……
“啊~!救命啊!Solmyr
你又用文件夾砸我!”
“愚蠢者是應(yīng)該受到懲罰的。”
畫(huà)外音:
…… 呃,好吧,我得承認(rèn)有點(diǎn)小小的例外。這里是一家軟件公司,發(fā)出慘叫的這位是
zero
,新進(jìn)的大學(xué)生;這邊一臉優(yōu)雅,看上去很有修養(yǎng)一點(diǎn)也不象剛剛砸過(guò)人的這位,是
Solmyr ,資深程序員,負(fù)責(zé)
zero 這一批新人的培訓(xùn)。啊,故事開(kāi)始了
……
“我干了什么啦?”zero
揉著鼻子問(wèn)道,“這次你拿來(lái)砸我的文件夾又大了一號(hào)!”
“你過(guò)來(lái)自己看看你犯下的錯(cuò)誤。”Solmyr
翻出了 zero
剛剛交上來(lái)的一段代碼:
……
char* msg = “Connectting ... Please wait“
……
if(
Status == S_CONNECTED )
strcpy(msg, “Connectted“);
……
“我犯了什么錯(cuò)誤啦?這是一個(gè)很平凡的字符串聲明而已”,zero
不滿的說(shuō)到。
“你看不出來(lái)嗎?connect
這個(gè)單詞的進(jìn)行時(shí)和過(guò)去時(shí)你都拼錯(cuò)了,多打了一個(gè)
t”,Solmyr
不緊不慢地回答。
“就為了這個(gè)你又用文件夾砸我
…… 啊!這次又是光盤盒!”
“這是商用軟件,你以為是在
QQ 上和
PPMM
聊天,有錯(cuò)別字不要緊啊?更糟糕的是,我故意留了這么長(zhǎng)的時(shí)間給你,到現(xiàn)在你還沒(méi)發(fā)現(xiàn)你真正的錯(cuò)誤在什么地方。你可真不是一般的菜啊~”,Solmyr
故意拖了個(gè)長(zhǎng)音,滿意的看到
zero
處于爆發(fā)的邊緣,“好吧,讓我們從基礎(chǔ)開(kāi)始,C
語(yǔ)言中是怎樣處理字符串的?”
“這個(gè)我知道”,zero
顯得很有自信,“C/C++
語(yǔ)言中,字符串是一段連續(xù)的字符型內(nèi)存單元,每個(gè)單元存放一個(gè)字符,并用\0
作為結(jié)尾的標(biāo)記。”
“那么使用指針之前,我們應(yīng)當(dāng)
……”
“我們應(yīng)當(dāng)保證這個(gè)指針指向合法的內(nèi)存,要么指向一塊已經(jīng)存在的內(nèi)存,要么為它動(dòng)態(tài)分配一塊。”,zero
開(kāi)始露出得意的笑容 ——
這種程度的問(wèn)題,哈!
“好!那么你的代碼中
msg 這個(gè)指針指向哪里?”
笑容凝固了。
“這個(gè)
…… 呃 …… 我想 …… 它應(yīng)該指向一塊合法內(nèi)存,因?yàn)橐郧拔疫@么做的時(shí)候,它能工作
……”,zero 期期艾艾的說(shuō)。
“合法內(nèi)存?這塊內(nèi)存是誰(shuí)分配的?它有多大?生存周期多長(zhǎng)?有哪些特殊的性質(zhì)?”
“……”
“唉!”,Solmyr
重重的嘆了口氣,“我就知道會(huì)這樣。好吧,讓我們先從簡(jiǎn)單的開(kāi)始。”。Solmyr
飛快的鍵入了如下代碼:
char
msg[] = “Hello“;
char* pmsg = (char*)malloc(
sizeof(“Hello“) );
strcpy(pmsg, “Hello“);
“上面這些代碼你應(yīng)該都很清楚了:msg
是一個(gè)字符數(shù)組,C
語(yǔ)言保證會(huì)為它分配一段連續(xù)的內(nèi)存,并將其初始化為
“Hello“ 。pmsg
是一個(gè)字符指針,我們調(diào)用了
malloc 函數(shù)為它動(dòng)態(tài)分配了一塊內(nèi)存,并用
strcpy 函數(shù)填充其值為
“Hello“
。這兩種做法的共通點(diǎn)是:首先用正常手段獲得一段內(nèi)存,然后填充值。接著再來(lái)看這個(gè):”
char* msg =
“Hello“;
“這一句代表什么意思?首先
msg 是個(gè)指針,C/C++
語(yǔ)言不負(fù)責(zé)為它分配一塊內(nèi)存;其次我們也沒(méi)有顯式的為它分配一塊內(nèi)存。它指向哪里?指向
“Hello“
,就是你直接寫(xiě)在代碼里的那一個(gè)。”
“什么叫做‘直接寫(xiě)在代碼里的那一個(gè)’?”,zero
露出了困惑的表情
“舉個(gè)例子你就明白了:”,Solmyr
再鍵入:
double
db = 1.5;
“這 一行里面,1.5
是個(gè)什么東西?它是一個(gè) double
類型常量,C/C++
語(yǔ)言要處理它們,也要分配內(nèi)存來(lái)存放這些東西。同理,當(dāng)你在代碼里寫(xiě)了
“Hello“ ,實(shí)際上
C/C++
語(yǔ)言就分配了一塊內(nèi)存存放這個(gè)字符串,當(dāng)你寫(xiě)
char* msg = “Hello“
的時(shí)候,你就是把這樣一塊內(nèi)存的地址賦給了指針
msg 。所以
msg
確實(shí)指向一塊合法內(nèi)存,這是有時(shí)候這段代碼能夠工作的原因。但是這樣做,其中蘊(yùn)涵了許多問(wèn)題,我來(lái)問(wèn)你,指向這塊內(nèi)存的指針應(yīng)該是什么類型?”
“當(dāng)然是
char*”,zero
不加思索的回答。
“錯(cuò)!應(yīng)該是
const char*
。想當(dāng)然耳,寫(xiě)在程序中的字符串你不希望它發(fā)生變化,所以很明顯的,這塊內(nèi)存應(yīng)該被解釋為常量。但是你在聲明
msg 的時(shí)候做了什么?”
“呃
…… 我用了一個(gè)非常量的指針去指向了一個(gè)常量字符串。”,這一次,zero
明顯的審慎多了。
“正確。看你原來(lái)的代碼,你不僅用一個(gè)非常量指針指向它,而且還對(duì)這個(gè)指針執(zhí)行了
strcpy
,往里寫(xiě)了內(nèi)容。在我們的編譯器上,這么做會(huì)引發(fā)什么后果?”
“呃
…… 引發(fā)一個(gè)運(yùn)行時(shí)錯(cuò)誤?”
“部分正確。準(zhǔn)確的講,只有在工程編譯選項(xiàng)為調(diào)試版本的時(shí)候,如果工程編譯選項(xiàng)為發(fā)布版本,一切都很正常
—— 奇怪嗎?并不,記住這一點(diǎn):C/C++
允許你打破任何保護(hù)。所以如果這兩行代碼在調(diào)試的時(shí)候沒(méi)有被發(fā)現(xiàn)而溜進(jìn)了發(fā)布版本里”,說(shuō)到這,Solmyr
狠狠的瞪了 zero
一眼,“將會(huì)是很難發(fā)現(xiàn)的。”
“可是說(shuō)來(lái)說(shuō)去這么做還是沒(méi)有什么危害不是嗎?msg
指向一塊合法內(nèi)存,內(nèi)容正確,而且也并不是真的不能寫(xiě)入,有什么好擔(dān)心的呢?”,zero
抱怨道。
Solmyr
順手抓起杯子,zero
反射性的立刻縮頭護(hù)臉。“別擔(dān)心,我只是喝水而已。”,Solmyr
面無(wú)表情 —— 如果忽略他嘴角那一絲壞笑的話
—— 的說(shuō)到,“沒(méi)有危害是嗎?看看下面的代碼:”
char* str1 =
“Hello“;
char* str2 = “Hello“;
*str1 = ‘P‘;
cout
<< str2 << endl;
“猜猜運(yùn)行結(jié)果是什么?”,Solmyr
一邊調(diào)整工程設(shè)置,一邊問(wèn)道。
“這還用問(wèn)嗎?當(dāng)然是輸出
Hello 了。”
“回答錯(cuò)誤,正確答案是
……”,Solmyr
按下了運(yùn)行按鈕,屏幕顯示的居然是
Pello !。
zero
大為詫異,撓著頭試圖找出其中的邏輯,突然間靈光一閃:“我明白了!str1
和 str2
實(shí)際指向同一段內(nèi)存!因?yàn)?C/C++
語(yǔ)言在處理 Hello
字符串的時(shí)候把它當(dāng)作常量,所以就做了優(yōu)化,只保存了一份
Hello !是不是這樣!”zero
興奮的轉(zhuǎn)向 Solmyr。
“嗯,
看起來(lái)有時(shí)候你也不是那么菜么”,Solmyr
贊許的點(diǎn)頭,“不過(guò)你還是說(shuō)錯(cuò)了一點(diǎn):這個(gè)不是
C/C++
語(yǔ)言的做法,是這個(gè)編譯器的做法。簡(jiǎn)單的說(shuō),你如果要對(duì)這種字符串寫(xiě)的話,其結(jié)果如何,是沒(méi)有定義的。所謂沒(méi)有定義,就是
C/C++
語(yǔ)言不保證會(huì)得到怎樣的結(jié)果,可能這樣也可能樣,完全決定于你的編譯器作者怎么想。想想看吧,哪天你的程序出現(xiàn)了古怪的問(wèn)題
—— 比如顯示信息出現(xiàn)了混亂 ——
起因卻是你在無(wú)關(guān)的地方寫(xiě)了一個(gè)字符串,會(huì)怎樣?這是維護(hù)時(shí)最大的惡夢(mèng)之一。現(xiàn)在你明白危害在哪里了?”
zero
有如大夢(mèng)初醒一般忙不迭地點(diǎn)頭:“我知道了,我知道了。”
“知道了還不快去改!”
……
zero
跑回坐位修改他的程序去了,辦公室里再度恢復(fù)了寧?kù)o,所有的人都埋頭于他們的工作之中。只有
Solmyr
一邊喝著咖啡一邊揉著太陽(yáng)穴,喃喃地吐出不祥的詞句:“這樣的日子才剛剛開(kāi)始啊
……”