網路城邦
上一篇 回創作列表 下一篇   字體:
Pointer 學習指引:Pointer is Address. That's it!
2009/09/17 22:25:46瀏覽9626|回應0|推薦1
本篇內容摘錄自筆者的著作《C 語言之修煉與實踐》之第五章

在現實的世界裡,不論是公寓大樓或是透天的樓房,房子本身會佔據空間,而其大小則是以面積來論定 (地坪幾坪、建坪幾坪、公共設施幾坪等等)。除此之外,房子本身一定有自己的地址,以方便其他的人或事物 (朋友、信件、貨品等) 能夠根據地址而到達此地。

C 語言中的 data object 就如同現實世界裡的房子,它會耗費 memory storage,同樣地也有自己的地址,否則我們無從對它執行讀取或寫入的運作。由於現今的作業系統都是以虛擬記憶體 (virtual memory) 的機制來運用與管理硬體記憶體,而且絕大部分都是以 byte 來做為 virtual memory 的計量單位;據此可知,data object 的地址就是一個 virtual address,而 virtual address 實際上就是 memory byte 的編號。

以房子的隱喻而言,C 語言的 pointer 就是一筆地址,它讓我們可以根據其內容循著地圖而找到該棟房子。正式的說法則是:pointer 是一個用來存放 address 的變數,它的用途就是用來記住某個 data object 住在什麼地方。

C 語言的 pointer 之宣告主要包含三個部分:一、資料型態 (data type);二、星號 (asterisk);三、變數名稱 (variable name)。就 data type 而言,其主要目的是在選擇以何種觀點去解釋記憶體的內容。就 asterisk 而言,如果 pointer 的宣告中含有 n 個 asterisk,則表示要做 n 次的 indirection (又稱為 dereference) 才抓得到 data object;如果 indirection 的次數不足 n 次,則表示抓到的東西是 pointer (即 address)。

Pointer Operators

C 語言有兩個 pointer operator,分別為:一、address of operator (&);二、indirection operator (*)。address of operator 是用來取得 data object 的地址 (address),而且不論是 single-byte 或是 multi-byte 的 data object,一律以其 starting byte 的位址來做為該 data object 的位址。indirection operator 是用來取得 data object 的 L-value 或 R-value,常見的用法為讀取 data object 的內容值 (亦即取得 data object 的 R-value)。以下列的 code snippet 為例:

1: int *ptr_val, val, result;
2:
3: ptr_val = &val;
4: *ptr_val = 100;
5: val++;
6: result = *ptr_val + 100;

其中 line 1 的 int *ptr_val 是一個資料型態為 int、變數名稱為 ptr_val 的 pointer variable,而 val 與 result 則是資料型態為 int 的 variable。line 3 的 &val 的意思是以 address of operator (&) 取得 val 的 address,故整個 ptr_val = &val 的意思是將 val 的 address 設定成 ptr_val 的內容值。line 4 位於 assignment operator 左邊的 *ptr_val 的意思是:取得 *ptr_val 的 L-value 後,對它執行寫入的運作,整個敘述等同於將 100 設定成 val 的內容。line 6 位於 assignment operator 右方的 *ptr_val 則是代表讀取 *ptr_val 的 R-value,整個敘述等同於讀取 val 之值,將它加上 100 之後設定給變數 result (此時 result 的內容為 101 + 100 = 201)。

程式解說:ptr_is_addr.c

本程式的主要目的為:展示 pointer 與 address 之間的關係。由於 pointer 實際上就是 address,而 address 是 memory byte 的編號,又因為編號實質上就是一個 integer,因此我們亦可使用 integer 來記下某個 data object 的 address,但也必須記住該 address 是住著何種資料型態的 data object,如此才能在執行 indirection 之際、知道要以何種觀點去解釋記憶體的內容值。

本程式宣告了三個變數來記住 ival 的位址,分別是:一、line 6 的 vptr,型態為 void*;二、line 7 的 addr,型態為 long;三、line 8 的 iptr,型態為 int*。其中的 iptr 於 line 10 取得 ival 的位址,vptr 於 line 11 取得 ival 的位址,addr 則於 line 12 取得 ival 的位址。line 11 的 (void *) &ival 的意思是:取得 ival 的位址後將其資料型態轉成 void *;同理,line 12 的 (long) &ival 是表示取得 ival 的位址後將其資料型態轉成 long。line 18 的 *((int*)vptr) 與 line 19 的 *((int*)addr) 的意思是:先轉換觀點之後再取值;亦即先將 vptr 與 addr 轉換成 (int *) 之後,再以 int 的觀點去解讀記憶體的內容值。

Generic Pointer

在某些情況下,我們只是想單純地把 data object 的 address 記下來,而不在乎它的 data type 為何,則此時該如何宣告 pointer variable 的 data type?沒錯!將它的 data type 宣告成 void 就行,此即所謂的 generic pointer。generic pointer 的宣告語法如下:

void *vptr = (void *) &data_object;

至於什麼時候會用到 generic pointer?典型的通例為:幫別人保管 data object 的 address 時會用到。那什麼樣的應用程式需要幫別人保管 data object 的 address 呢?典型的例子就是 Window System (X Window、Microsoft Window等等),例如某個 client application 需要對 Button Press Event 做出回應,因此它以自己的某個函式與某個 data object 向 Window System 登錄 Button Press Event (透過 Window System 所提供的 RegisterEvent 函式,把自己的函式與 data object 的 address 傳給 Window System),假設該函式與 data object 叫做 NotifyMe 與 my_obj,則 Window System 可利用 function pointer 與 generic pointer 將 NotifyMe 與 my_obj 的 address 記下來,等到 Window System 偵測到 Button Press Event 時,它就會將 my_obj 的 address 做為 NotifyMe 的一個 paramter,然後去呼叫 NotifyMe;換句話說,Window System 是以 callback 的方式去通知 client application 已經發生了一個 Button Press Event,並透過 callback function 中的 parameter,把 data object 的 address 傳回給 client application。

Pointer Arithmetic

當某個參選的政治人物選情告急時,他/她很可能就會到自己較有把握的選區做一家一家的拜訪,以穩固票源;當其選情如日中天時,他/她所選擇的拜訪方式就比較有可能是重點式的拜訪。

以電腦科學的術語來說,「一家一家的拜訪」就類似於「循序的運作」,而「重點式的拜訪」則類似於「選擇性的隨機運作」。同樣地,應用程式亦經常碰到「循序運作」與「選擇性隨機運作」的需求,而 pointer arithmetic 正可滿足這兩樣的需求,它使得應用程式能夠對 data object pool 執行循序的運作,或是以相對的距離來執行選擇性的隨機運作。pointer arithmetic 的基本運算式為:

pointer ± n = (char*) pointer ± n * sizeof(*pointer)

其中的 sizeof(*pointer) 為 pointer 所指向的 data object 之大小;換句話說,「pointer 加減 1」是以 pointer 所指向的 data object 之大小為其計算的單位。

Pointer versus Array

pointer 最常被使用的方式之一,就是對某塊記憶體執行循序或隨機式的存取運作,而 array 同樣地也可對某塊記憶體執行循序或隨機式的存取運作;據此似可推測兩者之間應該具有某種程度的關係,而實際上的情況也的確是如此。本章的開文曾經提到:所有的 array 運作都可轉換成 pointer 運作。此乃肇因於 array name 本身就是一個 pointer,compiler 在做編譯時是將它視為「永遠指向第一個 array element 的 constant pointer」;所以,array[i] 的運作實質上就是 pointer arithmetic 與 dereference 的合成運作。array 與 pointer 的宣告以及互相對應的轉換規則如下:

DataType array_name[n], *pointer;
Rule 1: array_name[i] = *(array_name + i)
Rule 2: pointer = array_name; array_name[i] = *(pointer + i);

Pointer to Multiple Objects

到目前為止,我們所示範的 pointer variable 都是指向「一個」data object,故其計量單位都是一個 data object 的大小,如果想要以「若干個」data object 的大小來做為 pointer variable 的計量單位,則其宣告的語法為:

DataType (*ptr_multiple_obj)[element_count];

請務必留意宣告中的 parentheses,少了它們就不是指向若干個 data object 的 pointer,而是 array of pointer。

Pointer to Function

由函式的呼叫語法可知,函式的名稱即代表該函式程式碼的入口,其本質就是一個 address;又根據 pointer 就是 address 的事實,可進而推知除了 data object 之外,pointer 亦可指向函式。指向某個函式的 pointer variable,其宣告與呼叫的方式如下:

DataType (*function_pointer)(DataType arg1, DataType arg2, ...)
(*function_pointer)(arg1, arg2, ...);
( 創作其他 )
推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=chungchia&aid=3326856