[C 語言] 程式設計教學:指標(Pointer) 和記憶體管理(Memory ...
文章推薦指數: 80 %
指標(pointer) 是C 語言的衍生型別之一。
指標的值並非資料本身,而是另一塊記憶體的虛擬位址(address)。
我們可利用指標間接存該指標所指向的記憶體的值。
在C 語言中, ...
Togglenavigation開源教學精選項目C語言Golang資料結構網頁程式電子書籍現代C語言程式設計C語言應用程式設計多平台Objective-C程式設計跨平台CommonLisp程式設計社群媒體臉書粉絲團臉書社團推特GitHubGumroad本站資訊關於著作權免責聲明隱私權開源教學C程式設計指標(Pointer)和記憶體管理(MemoryManagement)最後修改日期為JUL12,2018前言指標(pointer)是C語言的衍生型別之一。
指標的值並非資料本身,而是另一塊記憶體的虛擬位址(address)。
我們可利用指標間接存該指標所指向的記憶體的值。
在C語言中,有些和記憶體相關的操作必需使用指標,實作動態資料結構時也會用到指標,所以我們學C時無法避開指標。
許多C語言教材將指標放在整本書的後半段,集中在一章到兩章來講。
但我們很早就介紹指標,並在日後介紹其他C語言特性時,順便提到指標相關的內容。
這樣的編排,是希望讀者能儘早習慣指標的使用方式。
記憶體階層(MemoryLayout)使用指標,不必然要手動管理記憶體。
記憶體管理的方式,得看資料在C程式中的記憶體階層而定。
C語言有三種記憶體階層:靜態記憶體配置(staticmemoryallocation)自動記憶體配置(automaticmemoryallocation)動態記憶體配置(dynamicmemoryallocation)靜態記憶體儲存程式本身和全域變數,會自動配置和釋放,但容量有限。
自動記憶體儲存函式內的局部變數,會自動配置和釋放,在函式結束時自動釋放,容量有限。
動態記憶體儲存函式內的變數,需手動配置和釋放,可跨越函式的生命週期,可於執行期動態決定記憶體容量,可用容量約略等於系統的記憶體量。
雖然靜態記憶體和自動記憶體可自動配置,但各自受到一些限制,故仍要會用動態記憶體。
靜態記憶體配置(StaticMemoryAllocation)我們來看一個使用靜態記憶體配置的簡短範例:#include
由於該變數屬於全域變數,會自動配置記憶體,不需人為介入。
在範例程式的第6行中,我們宣告了指向int的指標i_p。
int*表示指向int型別的指標。
C語言需要知道指標所指向的型別,才能預知記憶體的大小。
&i代表回傳變數i的虛擬記憶體位址。
因為指標的值是記憶體位址,在本例中,我們的指標i_p指向一塊已經配置好的記憶體,即為變數i所在的位址。
接著,我們在範例的第7行確認確認指標i_p所指向的值的確是3。
在這行敘述中,*i_p代表解址,解址後會取出該位址的值。
在本例中即取回3。
接著,我們在第8行藉由修改指標i_p所指向的記憶體間接修改變數i的值。
最後,我們在第9行藉由assert(i==4);敘述確認指標i_p確實間接修改到變數i。
代表兩者的確指向同一塊記憶體。
如果讀者用Valgrind或其他記憶體檢查軟體去檢查此程式,可發現此範例程式沒有配置動態記憶體,也沒有記憶體洩露的問題。
代表使用指標不必然要手動管理記憶體。
附帶一提,在本範例中,我們使用了全域變數。
這在撰碼上是不良的習慣,因全域變數很容易在不經意的情形下誤改。
我們的程式只是為了展示靜態記憶體的特性,不建議在實務上使用全域變數。
自動記憶體配置(AutomaticMemoryAllocation)接著,我們來看一個使用自動記憶體配置的實例:#include
由於變數i存在於主函式中,故使用自動記憶體配置。
在本例的第5行中,我們宣告了指向變數i的指標i_p。
這時候i的記憶體已經配置好了。
這個範例除了記憶體配置的方式外,其他的指令和前一節的範例雷同,故不再詳細說明,請讀者自行閱讀。
如果讀者用Valgrind或其他記憶體檢查軟體檢查此範例程式,同樣可發現此程式沒有配置動態記憶體,也沒有記憶體洩露的問題。
動態記憶體配置(DynamicMemoryAllocation)我們來看一個動態記憶體配置的實例:#include
C標準函式庫中配置記憶體的函式為malloc(),該函式接收的參數為記憶體的大小。
我們甚少手動寫死記憶體的大小,而會使用sizeof直接取得特定資料型別的大小。
這幾乎是固定的手法了。
malloc()回傳的型別是void*,即為大小未定的指標。
我們會自行手動轉型為指向特定型別的指標。
有些C編譯器會自動幫我們轉型,就不用自行轉型。
由於配置記憶體是有可能失敗的動作,我們在第7行至第10行檢查是否成功地配置記憶體。
當malloc()失敗時,會回傳NULL。
而NULL在布林語境中會視為偽,故!i_p在i_p的值為NULL時會變為真。
若!i_p為真,代表malloc()未成功配置記憶體,這時候我們會中止一般的流程,改走錯誤處理流程。
我們先在標準錯誤印出錯誤訊息,然後用goto跳到標籤ERROR所在的地方。
由於C沒有內建的錯誤處理機制,使用goto跳離一般程式流程算是窮人版的例外(exception)。
fprintf()敘述用到了標準輸出入和巨集的概念,稍微超出現在的範圍。
先知道該敘述會在標準錯誤印出訊息所在的檔案名稱和行數即可。
如果malloc()成功地配置記憶體,我們就繼續一般的程式流程。
我們在第11行將i_p指向的記憶體賦值為3,然後在第12行至第15行檢查是否正確地賦值。
一般來說,這行敘述是不需檢查的。
這裡僅是展示這項特性。
由於i_p所指向的記憶體是手動配置的,我們在第16行釋放i_p所占用的記憶體。
基本上,malloc()和free()應成對出現。
每配置一次記憶體就要在確定不使用時釋放回去。
由於這個範例相當短,這似乎顯而易見。
但是在撰寫動態資料結構時,會跨越多個函式,比這個範例複雜得多,就有可能會忘了釋放記憶體。
當程式發成錯誤時,我們會改走錯誤處理的流程。
在本例中,錯誤流程在第18行至第21行。
在進行錯誤處理時,我們同樣會釋放記憶體,但程式最後會回傳非零值1,代表程式異常結束。
由於在錯誤發生時,我們無法確認i_p是否已配置記憶體,所以要用if敘述來確認。
請讀者再回頭把整個程式運行的流程看一次,即可了解。
空指標(NullPointer)當C程式試圖去存取系統資源時,該指令有可能失敗,故要撰寫錯誤處理相關的程式碼。
以下範例程式試圖打開一個文字檔案file.txt:#include
當檔案無法開啟時,會回傳空指標NULL。
所以我們在第6行至第9行檢查指標fp是否為空。
當fp為空指標時,!fp會負負得正,這時候程式會中止一般流程,改走錯誤處理流程。
在這個範例中,我們在終端機印出錯誤訊息,並回傳非零值1代表程式異常結束。
以下是另一種檢查空指標的寫法:fp=fopen("file.txt","r");
if(fp==NULL){
fprintf(stderr,"Failedtoopenthefile\n");
return1;
}
基本上,兩者皆可使用,這僅是風格上的差異。
比較指標是否相等當我們使用指標時,會處理兩個值,一個是指標所指向的位址,另一個是指標所指向的值。
我們用以下範例來看兩者的差別:#include
雖然變數a和變數b的值是相同的,但兩者存在於不同的記憶體區塊。
接著,我們第6行至第8行宣告三個指標,分別指向這兩個變數。
我們可以預期p和q同時會指向變數a所在的記憶體,而r則指向變數b所在的記憶體。
但p、q、r所指向的值皆相等。
從範例程式中即可確認這樣的狀態,讀者可自行閱讀一下。
野指標(WildPointer)若我們宣告了指標但未對指標賦值,這時候指標的位址是未定義的,由各家C編譯器自行決定其行為。
宣告但未賦值的指標稱為野指標,這時候指標的值視為無意義的垃圾值,不應依賴其結果。
我們來看一個野指標的範例程式:#include
若讀者跟著我們這個系列的文章讀下來,到目前為止我們還沒講過巨集。
我們這裡使用巨集是為了節省版面。
請讀者暫時把巨集當成另類函式即可,後文會再說明。
在第5行時,指標i_p尚未賦值,其值為垃圾值。
使用不同C編譯器編譯此範例程式時,會得到不同的結果。
在第12行時,我們將i_p以NULL賦值,這時候i_p就不再是野指標,轉為空指標。
在第19行時,我們手動配置了一塊記憶體,這時候i_p就是一般的指標。
由這個例子可知,我們在宣告指標時,若未馬上配置記憶體或其他系統資源時,應該立即以NULL賦值,讓該指標成為空指標。
迷途指標(DanglingPointer)原本指向某塊記憶體的指標,當該記憶體中途消失時,該指標所指向的位址不再合法,這時候的指標就成為迷途指標。
如同野指標,迷途指標所指向的值視為垃圾值,不應依賴其結果。
以下是一個迷途指標的範例程式:#include
但在第8行時,該區塊結束,i的記憶體會自動釋放掉,這時候i_p所指向的記憶體不再合法,i_p變成迷途指標。
之後的運算基本上是無意義且不可靠的。
我們再來看另一個迷途指標的例子:#include
在第10行時這塊記憶體釋放掉了,這時候i_p所指的位址不再合法,故i_p成為迷途指標。
即使之後的運算能夠成功,那也只是一時僥倖而已。
結語在本文中,我們介紹了數個指標的基本用法。
我們把指標放在前半部,是為了要讓大家及早適應指標。
在後續介紹各種C語言的特性時,我們會再加入指標相關的使用方式。
電子書籍如果你覺得這篇C語言的技術文章對你有幫助,可以看看以下完整的C語言程式設計電子書:分享本文追蹤本站
延伸文章資訊
- 1C語言: 超好懂的指標,初學者請進~ - 寫點科普Kopuchat
等等,那C 語言中的指標是長什麼樣子? 讓我們來看看這段程式碼: void main(){ int a = 15; int b = 2; int c = 39; ...
- 2C語言筆記— 指標(Pointers)
指標這個觀念是「陣列、字串、資料結構、演算法」的基礎,之後使用他們時,指標在裡面有很大的作用。 指標也算是一種變數,只是裡面存的不是一般的「數字 ...
- 3第5章、陣列和指標 - C/C++
指標(pointer)是一種用來指示資料存在於記憶體中的位址標示器,其宣告方式為資料型別*變數名稱; 資料型別通常是基本的資料型別,如int, float, char等,變數名稱與一般變數 ...
- 4[C 語言] 程式設計教學:指標(Pointer) 和記憶體管理(Memory ...
指標(pointer) 是C 語言的衍生型別之一。指標的值並非資料本身,而是另一塊記憶體的虛擬位址(address)。我們可利用指標間接存該指標所指向的記憶體的值。在C 語言中, ...
- 5指標入門-淺談指標的藝術@ Bryan的C語言筆記 - 隨意窩