官术网_书友最值得收藏!

3 挑戰指針(harib01c)

前面說過“C語言中沒有直接寫入指定內存地址的語句”,實際上這不是C語言的缺陷,因為有替代這種命令的語句。一般大多數程序員主要使用那種替代語句,像這次這樣,做一個函數write_mem8的,也就只有筆者了。如果有替代方案的話,大家肯定想用一下,筆者也想試試看。

write_mem3(i, i & 0x0f);

替代以上語句的是:

*i = i & 0x0f;

兩個語句有點像,但又不盡相同。不管那么多了,先換成后面一種寫法看看吧。好了,改完了,用“make run”命令運行一下。唉?奇怪,怎么會出錯呢?

invalid type argument of `unary *'

類型錯誤?

■■■■■

沒錯,就是類型錯誤。這種寫法,從本質上講沒問題,但這樣就是無法順利運行。我們從編譯器的角度稍微想想就能明白為什么會出錯了。回想一下,如果寫以下匯編語句,會發生什么情況呢?

MOV [ 0x1234], 0x56

是的,會出錯。這是因為指定內存時,不知道到底是BYTE,還是WORD,還是DWORD。只有在另一方也是寄存器的時候才能省略,其他情況都不能省略。

其實C編譯器也面臨著同樣的問題。這次,我們費勁寫了一條C語句,它的編譯結果相當于下面的匯編語句所生成的機器語言,

MOV [i], (i & 0x0f)

但卻不知道[i]到底是BYTE,還是WORD,還是DWORD。剛才就是出現了這種錯誤。

那怎么才能告訴計算機這是BYTE呢?

char *p; /*,變量p是用于內存地址的專用變量*/

聲明一個上面這樣變量p, p里放入與i相同的值,然后執行以下語句。

*p = i & 0x0f;

這樣,C編譯器就會認為“p是地址專用變量,而且是用于存放字符(char)的,所以就是BYTE.”。順便解釋一下類似語句:

char *p; /*用于BYTE類地址*/
short *p; /*用于WORD類地址*/
int *p; /*用于DWORD類地址*/

這次我們是一個字節一個字節地寫入,所以使用了char。

既然說到這里,那我們再介紹點相關知識,“char i; ”是類似AL的1字節變量,“short i; ”是類似AX的2字節變量,“int i; ”是類似EAX的4字節變量。

而不管是“char *p”,還是“short *p”,還是“int *p”,變量p都是4字節。這是因為p是用于記錄地址的變量。在匯編語言中,地址也像ECX一樣,用4字節的寄存器來指定,所以也是4字節。

■■■■■

這樣準備工作就OK了。再用“make run”運行一遍以下內容。

void HariMain(void)
{
    int i; /*變量聲明。變量i是32位整數*/
    char *p; /*變量p,用于BYTE型地址*/

    for (i = 0xa0000; i <= 0xaffff; i++) {

        p = i; /*代入地址*/
        *p = i & 0x0f;

        /*這可以替代write_mem8(i, i & 0x0f); */
    }

    for (; ; ) {
        io_hlt();
    }
}

哇,居然不使用write_mem8就能顯示出條紋圖案,真是太好了。

嗯?且慢!仔細看看畫面,發現有一行警告。

warning: assignment makes pointer from integer without a cast

這個警告的意思是說,“賦值語句沒有經過類型轉換,由整數生成了指針”。其中有兩個單詞的意思不太明白。類型轉換是什么?指針又是什么?

類型轉換是改變數值類型的命令。一般不必每次都注意類型轉換,但像這次的語句中,如果不明確進行類型轉換,C編譯器就會每次都發出警告:“喂,是不是寫錯了?”順便說一下,cast在英文中的原意是壓入模具,讓材料成為某種特定的形狀。

指針是表示內存地址的數值。C語言中不用“內存地址”這個詞,而是用“指針”。在C語言中,普通數值和表示內存地址的數值被認為是兩種不同的東西,雖然筆者也覺得它們沒什么不同,但也只能接受這種設計思想了。基于這種設計思想,如果將普通整數值賦給內存地址變量,就會有警告。為了避免這種情況的發生,可以這樣寫:

p = (char *)i;

這就對i進行了類型轉換,使之成為表示內存地址的整數。(其實這樣轉換以后,數值一點都沒變,但對于C編譯器來說,類型的不同有著很大的差別。)以后再進行這樣的賦值時,就不會出現這種討厭的警告了。于是我們這樣修改一下。

再運行一次“make run”吧。好了,不再出現那種煩人的警告了。write_mem8已經沒用了,所以可以將它從naskfunc.nas中刪除。

這樣的寫法雖然有點繞圈子了,但我們實現了只用C語言寫入內存的功能。

COLUMN-2 只要使用類型轉換,就可以不用指針之類的方法嗎?

好不容易介紹完了類型轉換,我們來看一個應用實例吧。如果定義:

p = (char *) i;

那么將上式代入下面語句中。

*p = i & 0x0f;

這樣就能得到下式:

*((char *) i) = i & 0x0f;

這個語句執行起來毫無問題。雖然讀起來不是很容易理解,但這樣可以不特意聲明p變量,所以筆者偶爾還是會使用的。

有沒有覺得這種寫法與“BYTE[i] = i & 0x0f; ”有些相像嗎?在特別喜歡匯編語言的筆者看來,會有這種感覺呢。(笑)

COLUMN-3 還是不能理解指針

能有這種想法,說明你很誠實。那好,我們再盡量詳細地講解一下。

如果你曾經使用過C語言,并且聽說過“指針”這個詞,那么剛才的說明肯定讓你覺得混亂,摸不著頭腦。倒是那些從未接觸過C語言的人更能理解一些。

這里,特別重要的一點是,必須想點辦法讓C語言完成以下功能:

MOV BYTE [i], (i & 0x0f)

也就是,向內存的第i號地址寫入i & 0x0f的計算結果。而程序只是偶然地寫成了:

int i;
char *p;

p = (char *) i;
*p = i & 0x0f;

必須要先理解以上程序。這可能與你所知道的指針的使用方法完全不同,不過暫時先不要想這個。總之上面4行,是MOV語句的替代物,這一點是最重要的。

從沒聽說過C語言指針的人,僅僅會想“哦,原來C語言中是這么寫的,沒那么復雜么。”的確如此,沒什么不懂的嘛。

■■■■■

下面再稍微深入說明一下。我們常見的兩個語句是:

p = (char *) i;
*p = i & 0x0f;

這兩個語句有什么區別呢?這是不懂匯編的人常有的疑問。將以上語句按匯編的習慣寫一下吧。假設p相當于ECX,那么寫出來就是:

MOV ECX, i
MOV BYTE [ECX], (i & 0x0f)

它們的區別很清楚,即一個是給ECX寄存器賦值,一個給ECX號內存地址賦值。這完全是兩回事。存儲它們的半導體也不一樣,一個在CPU里,一個在內存芯片里。在C語言中,雖然p與*p只有一字之差,但意思上的差別卻如此之大。

如果執行順序調過來會怎么樣呢?也就是像這樣:

*p = i & 0x0f;
p = (char *) i;

不是很熟悉指針的人可能認為這樣也行。但是,這相當于:

MOV BYTE [ECX], (i & 0x0f)
MOV ECX, i

如果這么做,第一個MOV的時候,ECX的值不確定,是個隨機數,這會導致i & 0x0f的結果寫入內存的某個不可知的地址中。這樣的后果很嚴重。

■■■■■

另一個比較常見的疑問,是關于聲明的。在C語言中,如果不聲明變量就不能使用。所謂聲明,就是類似“int i; ”這種語句。有了這句話,變量i就可以使用了(與此不同的是匯編語言中,EAX, DL等,不聲明也可以自由使用)。在C語言中,聲明了10個變量,就可以用10個變量,這是理所當然的事。

既然如此,那為什么只聲明了“char *p; ”卻不僅能使用p,還可以使用*p呢?這讓人搞不懂……確實,這個程序中,給p和*p賦值了。看上去,能夠使用的變量數比實際聲明的變量數要多。

遇到這種情況時,我們先回到匯編語言中看看。

MOV ECX, i
MOV BYTE [ECX], (i & 0x0f)

看著這個程序,就不會再有人認為其中有2個變量了。其中只有一個ECX。而且,同樣是“MOV AL, [ECX]”, ECX是123的時候,和ECX是124的時候,放入AL的值也是不同的(只要這兩處地址存放的不是同樣的值)。這是因為地址不同,代表的內存區域不同。就好比不同的住址,住的人也不一樣。

所以,同樣是*p,因為p值的不同,記錄的值也不同。

*p = 3;
p = p + 3;
i = *p;

也就是說如果執行以上片段,i不一定是3,因為地址已經變了。

費了半天勁,其實筆者想說的就是,*p并不是什么變量。確實,我們可以給*p賦值,也可以引用*p的值,這看起來就像變量一樣。但即便如此,*p也不是一個變量,變量只有p。所謂*p,就相當于匯編中BYTE [p]這種語句的代替。

如果你還執拗地說*p是一個變量,那照這種邏輯,變量可遠不止2個,還有很多很多。因為只要給p賦上不同的值,*p就代表完全不同區域的內存內容。

■■■■■

下一個問題也是關于聲明的:“char *p; ”聲明的是*p,還是p呢?

這也是一個常見的問題。先給出結論吧,聲明的是p。“既然如此,那為什么不寫成char*p;呢?”有這種想法,說明你這方面的直覺很好。筆者也認為這樣寫對于初學者來說更簡單易懂。事實上,在C語言中寫成“char* p; ”也可以,既不出錯,也不出警告,運行也沒問題。

但這種寫法有點小問題。如果寫成“char* p, q; ”,我們看上去會覺得p和q都表示地址的變量,但C編譯器卻不那樣認為,q會被看作是一般的1字節的變量。也就是被解釋成“char*p, q”。為了避免這樣的誤解,一般的程序員不寫成“char* p; ”,所以筆者也按照這個習慣編寫程序。另外,如果想要聲明兩個地址變量,就寫成“char *q, *p; ”。

■■■■■

今天的專欄寫得好長呀,我們來整理總結一下吧。首先,本書中出現的“char *p; ”不必看作指針,這是最重要的決竅。p不是指針,而是地址變量。不要使用“p是指針”這種模棱兩可的說法,“p是地址變量”這種說法比較好。

將地址值賦給地址變量是理所當然的。并且,既然地址代表的是內存的地址,可以讓該地址存放自己想放的任何值。雖然也可以將地址變量說成是指針,但筆者聽到指針這個說法也很茫然,所以除了跟別人討論時以外,筆者也不說指針什么的。

C語言中地址變量的聲明,以及給內存地址賦值的,寫法不是很習慣,但終究這只是寫法的不同,思考問題的方法與匯編語言差不多。在C語言開發人員看來,“C語言的*p比匯編語言BYTE [p],更短小精悍”,確實,簡潔是一個長處,但就是因為簡潔,才讓初學者不好理解。

C語言的很多初學者都在學習指針時受挫,以至于會想“如果沒有指針就好了”。而事實上,沒有指針的語言也確實是存在的。但這種語言很不好用,因為沒有指針就無法往指定內存的地址存入數據,那怎么往VRAM上繪制圖像呢?這種語言只能讓寫操作系統變得更加困難。

筆者也認為,C語言指針的語法很難理解,所以希望能改善。但它像匯編語言一樣,能直接訪問地址,這一點非常好。所以希望大家能這樣想:“不是要廢除指針,而是把指針改善得更直觀易懂。”

主站蜘蛛池模板: 牡丹江市| 南乐县| 宝坻区| 福贡县| 伊春市| 汕尾市| 长顺县| 兴仁县| 美姑县| 临武县| 潼南县| 梅河口市| 太仓市| 台山市| 苏尼特左旗| 麟游县| 沂源县| 漳州市| 健康| 五华县| 盘锦市| 腾冲县| 易门县| 东阳市| 黄浦区| 高碑店市| 获嘉县| 汕尾市| 安陆市| 扶风县| 富平县| 富民县| 瑞昌市| 延边| 乐亭县| 年辖:市辖区| 焦作市| 荔波县| 东明县| 江口县| 巍山|