張?zhí)煊?/p>
(無錫科技職業(yè)學院智能制造學院,無錫214000)
C語言語法簡潔、運算符豐富、編程靈活、可移植性高,是一門重要的計算機語言。在C語言中,通過指針可以實現(xiàn)硬件的訪問、動態(tài)分配和收回內(nèi)存、減少全局變量的使用、實現(xiàn)函數(shù)的回調(diào)功能等,被稱為C語言的“靈魂”;但是指針概念抽象,難以把握,使用不當會導致程序退出和內(nèi)存泄露,甚至系統(tǒng)崩潰,成為學習C語言的難點。
在C Primer Plus[1]一書中,將指針定義為一種變量,其值為內(nèi)存地址(Basically,a pointer is a variable,(or more generally,a data object),whose value is a memory address)。通過這個定義,理解指針的前提是,理解變量和變量值,內(nèi)存和內(nèi)存地址。Data object is a general term for a region of data storage that can be used to hold values.The C standard uses just the term object for this concept.One way to identify an object is by using the name of a variable。數(shù)據(jù)對象指的是內(nèi)存的一部分,在C語言標準中稱為對象,定位該對象的方法之一是通過變量名。在A Reference Manual[2]中,指針被定義為,指向類型的對象,指針本身也是一種對象,該對象的值為內(nèi)存的地址(For any type T,a pointer type“pointer to T”may be formed.A value of pointer type is the address of an object or function of type T.)類型的定義:A type is a set of value and a set of operations on those values。類型是一個數(shù)值集合以及對這個數(shù)值集合的操作。指針在The C Programming Language[3]一種中的定義為:指針是一種變量,該變量的值時其他變量的內(nèi)存地址。(A pointer is a variable that contains the address of a vari?able.)Variables and constants are the basic data objects manipulated in a program。變量和常量是程序處理的兩種基本對象。A data object is a named region of storage.一個對象是一個命名的存儲區(qū)域。
分析上述C語言領(lǐng)域經(jīng)典資料對指針及其指針相關(guān)概念的定義可知,內(nèi)存是把握和理解“指針”概念的核心所在。在閻石教授所著的《數(shù)字電子技術(shù)基礎(chǔ)》中[4],把內(nèi)存定義為:一種能夠存儲大量二值信息(或稱為數(shù)據(jù))的器件[5]。本文利用開關(guān)的“閉”“開”兩種狀態(tài),來表示存儲器中的“二值信息”。利用64個“開關(guān)”,構(gòu)建了可論述的內(nèi)存模型。以此為基礎(chǔ)介紹了C語言中變量、類型、指針等概念的核心特點。
對于一個普通的物理開關(guān),存在兩種狀態(tài),開和關(guān);即通電和斷電兩種狀態(tài)。假設(shè)“開”的狀態(tài),用“0”來表示,“關(guān)”的狀態(tài)用“1”來表示,那么開關(guān)的兩種狀態(tài)就可以表示“1”和“0”。表述了內(nèi)存的特點:“二值信息”。若將8個開關(guān)排成一排,得到的結(jié)果如圖2所示。圖2中,底部的一行全部為關(guān)閉狀態(tài);中間一行,部分處于打開,部分處于關(guān)閉;上面一行處于全部打開狀態(tài)。因為每個開關(guān)存在0和1兩種狀態(tài),8個開關(guān)組合到一起,共存在256個狀態(tài)。即從全部關(guān)閉(0000 0000)到全部打開(1111 1111),共256種狀態(tài)。若將64個開關(guān),按照每一排有8個,則可以得到8行,得到結(jié)果如圖3所示,它形象地表示了內(nèi)存基本模型。
圖1 開關(guān)示意圖
圖2 開關(guān)不同狀態(tài)示意圖
圖3 開關(guān)“內(nèi)存模型”示意圖
結(jié)合圖2和圖3可以得出,這些開關(guān)共有256×8個狀態(tài)。我們把這些所有可能的狀態(tài)統(tǒng)稱為:“內(nèi)存狀態(tài)信息”。按照8個一排、縱向?qū)ζ涞囊?guī)則,對64個開關(guān)進行統(tǒng)一編碼,得到的結(jié)果如圖3所示,右側(cè)是按照10進制編碼的結(jié)果:0~7。這種結(jié)果稱為:內(nèi)存“地址信息”。
由圖3可知,所有的開關(guān)處于打開狀態(tài)。現(xiàn)在將第2行第3列、第5列和第7列(從左至右:0~7列)的按鈕閉合,得到的結(jié)果如圖4所示。上述描述的過程,在計算機領(lǐng)域用“操作”一詞來表述。這個操作過程有兩個基本的步驟:①選擇某些按鈕,②設(shè)置選擇按鈕的狀態(tài)(“開”、“閉”)。換而言之,通過內(nèi)存的“地址信息”,選擇某些按鈕;而后設(shè)置這些按鈕的狀態(tài)信息(“開”、“閉”),即選中內(nèi)存的“狀態(tài)信息”。這就是內(nèi)存操作的最本質(zhì)特點。
圖4 開關(guān)“內(nèi)存模型”操作示意圖
前述分析可知,一排8個的“開關(guān)”(如圖5黑色實線部分所示,標記為:內(nèi)存區(qū)域A),有256種狀態(tài)。換而言之,最多可以表示256個數(shù)。超過256,一排8個“開關(guān)”無法表達?,F(xiàn)實中需要表達的數(shù)量遠不止256個。為了增加可以表達的數(shù)量,可以增加“開關(guān)”個數(shù)。一種方式如圖5下部虛線方框所示“內(nèi)存區(qū)域B”(第1排和第0排))。當數(shù)量范圍在0~255時,可用一排的“開關(guān)”表示。視它們“開關(guān)”視為一組(圖5第5排)。在C語言中,用“內(nèi)存區(qū)域”指稱這一組開關(guān)(如圖5所示內(nèi)存區(qū)域A)。在對它們進行操作是,首選選中它們,而后改變它們的狀態(tài)。當數(shù)量范圍在0~255×255(65025),可以用 2排“開關(guān)”(圖 5 第 1排和第 0排,內(nèi)存區(qū)域B)進行表示,視它們?yōu)橐唤M。在對它們進行操作時,首選選中它們,而后改變它們的狀態(tài)。
在C語言中,通過類型來表達所用“開關(guān)”的數(shù)量。例如:unsigned char類型,表示視8個“開關(guān)”為一組(內(nèi)存區(qū)域A)。那么char可以表示的狀態(tài)總數(shù)為256。就C語言中char類型而言,用“取值范圍”一詞,指稱前述狀態(tài)總數(shù)。再例如,unsigned int16類型,表示視16個開關(guān)為一組(內(nèi)存區(qū)域B),可以表示的狀態(tài)總數(shù)為 65025,取值范圍為:0~65025。
當然,C語言中還有其他的數(shù)據(jù)類型,如浮點數(shù)、數(shù)組、結(jié)構(gòu)體等。為了降低論述的復(fù)雜程度,本文不再論述。注意,多數(shù)情況下,一次最小選擇的數(shù)量是1個開關(guān),這個叫位選。但是多數(shù)情況下,一次選擇的數(shù)量為8個開關(guān)。這里面涉及架構(gòu)知識,本文也不予展開。本文的目的理清指針概念的核心側(cè)面,而不是指針概念的全部。
圖5 開關(guān)“內(nèi)存模型”中的內(nèi)存區(qū)域示意圖
可以通過內(nèi)存的“地址信息”(圖5第5排),來選擇所需操作“開關(guān)”數(shù)量(即內(nèi)存區(qū)域);但是不方便而且也容易出錯。一個解決辦法就是,給相應(yīng)內(nèi)存區(qū)域命名。一種命名的結(jié)果如圖6所示。用“Char-1”表示含有8個開關(guān)的內(nèi)存區(qū)域,用“Int-1”表示含有16個開關(guān)的內(nèi)存區(qū)域。上述兩個名字,在C語言中稱為變量。由圖6可知,變量“Char-1”包含了相應(yīng)內(nèi)存區(qū)域的“地址信息”和“狀態(tài)信息”。換而言之,通過變量“Char-1”,可以獲得相應(yīng)內(nèi)存區(qū)域的地址編碼“5”,還可以獲得相應(yīng)內(nèi)存區(qū)域的狀態(tài)“閉閉閉閉閉閉閉開”。假設(shè)“閉”用“1”來表,“開”用“0”來表示,相應(yīng)內(nèi)存區(qū)域的狀態(tài)可以表示為“00000001”。若將這種狀態(tài)視為“2進制”,則表示的數(shù)值為:00000001;對應(yīng)的“10進制”為1。在C語言中,我們說變量“Char-1”的值為1。同樣的方法可以分析“Int-1”對應(yīng)的二進制數(shù)值為:1011101100001111,對應(yīng)的十進制為:47887。在C語言中,我們說變量“Int-1”的值為47887。
由圖6可知,變量“Char-1”對應(yīng)的內(nèi)存地址信息為5(十進制)。前述可知,變量“Char-1”的值為1(十進制)。在C語言中,變量“Char-1”的“數(shù)值”可以用Char-1來表示,而變量“Char-1”的地址,可用“&Char-1”來表示。換而言之,在C語言中,“Char-1”等價于“1”,表達的是“Char-1”對應(yīng)的內(nèi)存狀態(tài)信息;而“&Char-1”等價“5”,表達的是“Char-1”對應(yīng)的內(nèi)存地址信息。一個變量是一個命名了內(nèi)存區(qū)域(如內(nèi)存區(qū)域A)[1],包含了兩個基本的方面“內(nèi)存地址信息”和“內(nèi)存狀態(tài)信息”。
圖6 開關(guān)“內(nèi)存模型”中的變量示意圖
由圖6可知,變量“Int-1”對應(yīng)的地址信息為1和0(十進制),前述分析可知,變量“Int-1”的值為47887(十進制)。在 C 語言中“Int-1”等價于“47887”,“&Int-1”指稱/選中的內(nèi)存區(qū)域如由圖6實線方框所示。但是“&Int-1”得到的“值”可能是“0”,也可能是“1”。這與具體的CPU構(gòu)架有關(guān),不是本文關(guān)注的重點。本文關(guān)注的重點是“&Int-1”所指稱的內(nèi)存區(qū)域B。為了,論述方便,本文假設(shè)“&Int-1”得到的值為“1”。
在進行內(nèi)存操作時,存在通過“內(nèi)存區(qū)域A”找到“內(nèi)存區(qū)域B”的需求。換而言之,希望“內(nèi)存區(qū)域A”和“內(nèi)存區(qū)域B”相關(guān)聯(lián)。假如內(nèi)存區(qū)域A中,有“內(nèi)存區(qū)域B”的地址信息,就可以實現(xiàn)兩個內(nèi)存區(qū)域的關(guān)聯(lián)。前述分析可以,一個內(nèi)存區(qū)域包含兩個基本的方面:“地址信息”和“狀態(tài)信息”。內(nèi)存的地址信息,一般是不能改變的。換而言之,當把“開關(guān)”按照一定規(guī)則編碼后,這個編碼信息是基本不變的。方便改變的只有“狀態(tài)信息”,若內(nèi)存的狀態(tài)信息,可以表示內(nèi)存的地址信息,就可以實現(xiàn)兩個內(nèi)存區(qū)域的關(guān)聯(lián)。如內(nèi)存區(qū)域A的狀態(tài)信息,用二進制表示:0000 0001;內(nèi)存區(qū)域B的地址信息,用二進制表示也為:0000 0001。這樣就實現(xiàn)內(nèi)存區(qū)域A和內(nèi)存區(qū)域B的相互關(guān)聯(lián)。在本文中,開關(guān)代表的是二值信息,如果用開用“0”表示,關(guān)用“1”表示,得到的結(jié)果如圖8所示。直觀地表示了內(nèi)存地址信息,可以用內(nèi)存狀態(tài)信息來表達。上述內(nèi)容在C語言中表述為,指針變量的值用于存儲內(nèi)存地址[6]。
圖7 開關(guān)“內(nèi)存模型”中的指針變量示意圖
圖8 二值信息“內(nèi)存模型”中的字符型指針變量示意圖
在C語言中,內(nèi)存區(qū)域A和內(nèi)存區(qū)域B相互關(guān)聯(lián)的實現(xiàn)方式為:指針。下述語句“unsigned char*PChar-1”,定義了一個字符型指針變量“PChar-1”。假設(shè)變量“PChar-1”對應(yīng)的地址信息為:00000101,即內(nèi)存區(qū)域A標記的內(nèi)存單元?!癙Char-1”對應(yīng)的狀態(tài)信息為:00000001。這個狀態(tài)信息代表的是存地址信息,即00000001,即“內(nèi)存區(qū)域B”標記的內(nèi)存單元。在C語言中,“*PChar-1”用以表示“內(nèi)存區(qū)域B”對應(yīng)狀態(tài)信息,即 10111011。而“&(*PChar-1)”用以表示內(nèi)存區(qū)域B的地址信息。PChar-1稱為指針變量,簡稱指針。實際上“*PChar-1”代表的就是一個字符變量。當需要改變內(nèi)存區(qū)域B的狀態(tài)信息時,通過給“*PChar-1”賦值即可完成。例如,*PChar-1=255的結(jié)果如圖9所示。
如圖10所示,“內(nèi)存區(qū)域A”和“內(nèi)存區(qū)域B”的大小不一致。在這種情況下如將內(nèi)存區(qū)域A與內(nèi)存區(qū)域B聯(lián)系起來。在C語言中,也是通過指針實現(xiàn)兩個內(nèi)存區(qū)域的關(guān)聯(lián)。在這一關(guān)聯(lián)的過程中,需要知道內(nèi)內(nèi)存區(qū)域A和內(nèi)存區(qū)域B的一些基本信息,例如內(nèi)存區(qū)域B的大小。由前述分析可知,內(nèi)存區(qū)域的大小可以通過類型來確定。
圖9 二值信息“內(nèi)存模型”中的無符號字符型指針變量示意圖
圖10 二值信息“內(nèi)存模型”中的無符號整型指針變量示意圖
在 C 語言中,下述語句“unsigned int*PInt-1”,定義了一個無符號的16位整型指針變量“PInt-1”。假設(shè)變量“PInt-1”對應(yīng)的地址信息為:00000101,即內(nèi)存區(qū)域A標記的內(nèi)存單元?!癙Int-1”對應(yīng)的狀態(tài)信息為:00000001。這個狀態(tài)信息代表的是存地址信息,即00000001,即“內(nèi)存區(qū)域B”標記內(nèi)存單元的第一行。在C語言中“*PInt-1”,指稱/選中的是內(nèi)存區(qū)域B對應(yīng)的區(qū)域,盡管“&(*PInt-1)”得到的結(jié)果是 0000 0001。與“*PChar-1”類似,“*PInt-1”代表的就是一個 16 位的整型變量。當需要改變內(nèi)存區(qū)域B的狀態(tài)信息時,通過給“*PInt-1”賦值即可完成。例如,*PInt-1=65280的結(jié)果(圖10,內(nèi)存區(qū)域B所示)。注意PChar-1和PInt-1 對應(yīng)的值都是“1”,但是“*PChar-1”和“*PInt-1”的結(jié)果明顯不同是,原因是它們的類型不同,更根本的原因是PChar-1和PInt-1指稱/選中的內(nèi)存區(qū)域大小不同。
一般情況下,當內(nèi)存的位寬和大小確定后,指針變量占用的內(nèi)存區(qū)域大小是確定的。換而言之,當內(nèi)存的地址編碼結(jié)束之后,存儲每一個地址編碼信息所需內(nèi)存區(qū)域是一定的。在本文中,內(nèi)存是有64個開關(guān),按照8個一排構(gòu)成的模型(8,經(jīng)常被稱為位寬)。內(nèi)存的地址信息是:0~7。當編碼結(jié)束后,存儲內(nèi)存地址信息所需的開關(guān)個數(shù)也就確定了。如前述的PChar-1和PInt-1,存放它們所需的內(nèi)存區(qū)域大小是相同的。但是*PChar-1和*PInt-1所指稱/選中的內(nèi)存區(qū)域是不一樣。
前述分析可知,PChar-1的值為“1”,PInt-1的值也為“1”。PChar-1+1 的值為“2”,但是 PInt-1+1 的值為3。這是因為,它們指稱/選中的內(nèi)存區(qū)域大小不同;PChar-1指稱/選中的內(nèi)存區(qū)域是一排,而PInt-1選中的內(nèi)存區(qū)域是“兩排”。指針變量的運算結(jié)果,與指針變量所指內(nèi)存區(qū)域的大小有關(guān),如圖11和圖12所示。
本文以“開關(guān)”為元素,構(gòu)建了簡易的內(nèi)存模型。以該模型為基礎(chǔ),介紹了變量概念所包含的兩個基本側(cè)面“地址信息”和“狀態(tài)信息”,導出了內(nèi)存區(qū)域“狀態(tài)信息”表述內(nèi)存區(qū)域“地址信息”,是指針概念的本質(zhì)所在。從內(nèi)存的視角,分析了“變量類型”與“指針類型”的關(guān)系,進而解析了。C語言中“指針概念”的基本側(cè)面。
圖11 二值信息“內(nèi)存模型”中無符號字符型指針運算示意圖
圖12 二值信息“內(nèi)存模型”中無符號整型(uint-16) 指針變量的運算