指针_(计算机编程)

在電腦科學中，**指標**（英語：Pointer），是程式語言中的一類資料類型及其物件或變數，用來表示或儲存一個記憶體位址，這個位址的值直接指向（points to）存在該位址的物件的值。

## 歷史

在 1964 年，哈羅德・勞森發明了最早的指標。他在 PL/I 中實作出了這個概念，其他高級程式語言也很快跟進，使用了這個想法。指標（pointer）這個名稱首次出現在系統發展公司（System Development Corporation，SDC）的技術檔案，當中使用了堆疊指標（stack pointer）這個名詞。

## 概論

在計算機科學中，指標是一種最簡單形式的參照（reference）。

指標有兩種含義，一是作為資料類型，二是作為實體。前者如字元指標、浮點數指標等等；後者如指標物件、指標變數等。

指標作為資料類型，可以從一個函式類型、一個物件類型或一個不完備類型中導出。從中導出的資料類型被稱之為**被參照型態**（referenced type）。指標類型描述了一類的物件，物件值為對被參照類型的實體的參照。

C++ 標準中規定，「指標」概念不適用於成員指標（不包含指向靜態成員的指標）。C++ 標準規定，指標分為兩類：

* object pointer type：指向 void 或物件類型，表示物件在隨機存取記憶體中的字元位址或空指標。
* function pointer type：指代一個函式

指標參考（reference）了記憶體中一個位址。通過被稱為指標反參考（dereferencing）的動作，可以取出在那個位址中儲存的值。儲存在指標指向的位址中的值，可能代表另一個變數、結構、物件或函式。但是從指標值是無法得知它所參照的記憶體中儲存了什麼資料型別的資訊。可以打個比方，假設將電腦記憶體當成一本書，那麼一張記錄了某個頁碼加上行號的便利貼，可以被當成是一個指向特定頁面的指標；根據便利貼上面的頁碼與行號，翻到那個頁面，把那個頁面的那一行文字讀出來，就相當於是對這個指標進行反參考的動作。可做一類比以增強對指標的理解：整數（integral）也是一類資料類型及其物件或變數，可定義具體的資料類型如短整數（short）、長整數（long）、超長整數（long long）、無符號整數（unsigned）等等；也可以用於稱呼整數值、整數物件、整數變數等。又如，一個浮點數指標（float \*），可稱作指向了一個浮點數類型的物件。

在高階語言中，指標有效的取代了在低階語言（如組合語言與機器碼）直接使用記憶體位址。但它可能只適用於合法位址之中。因為指標更貼近硬體，編譯器能夠很容易的將指標翻譯為機器碼，這使指標操作時的負擔較少，因此能夠提高程式的執行速度。

使用指標能夠簡化許多資料結構的實作，例如在走訪字串，查取表格，控制表格及樹狀結構上。對指標進行複製，之後再解參照指標以取出資料，無論在時間或空間上，都比直接複製及存取資料本身來的經濟快速。指標表示法較為直覺，使程式的表達更為簡潔，同時也能夠提供動態機制來建立新的節點。

在程序式程式設計（procedural programming）中，指標也被用來儲存系統呼叫流程，以及動態連結資料庫（DLL）的進入點位址。在物件導向程式設計中，使用函式指標（Function pointer）來綁定方法（method），常見於虛擬方法表（Virtual method table）中。

但是指標本身也存在一些可被濫用之處，在存取某個資料結構時，可能會超出可用範圍，使軟體或作業系統出現異常，嚴重時可造成當機。利用指標去存取或修改非合法可取用的資料，也可能造成安全性問題。為此，C 與 C++ 語言規定指標類型為強型別，即指標值不僅是一個記憶體位址，同時它的資料類型說明了存在這個位址可以安全存取的位址的範圍，例如，float\* 可以存取 4 個字元的記憶體空間，double\* 可以存取 8 個字元的記憶體空間。

許多程式語言中都支援某種形式的指標，最著名的是 C 語言，但是有些程式語言對指標的運用採取比較嚴格的限制。因為指標的機制比較簡單，其功能可以被集中重新實作成更抽象化的參照（reference）資料形別，如 Java 一般避免用指標，改為使用參照。

## 指標的實質

C99 與 C++11 標準分別明確規定了把一個指標值轉換自（from）/ 成（to）整形是允許的，但整型的大小至少不低於 `std::intptr_t`。

在 C 語言的多數實現中，指標值是一個以當前系統定址範圍為取值範圍的整數。

32 位元系統的定址能力（位址空間）是 4GB（0~232-1），以二進位表示時長度為 32 位元，每格儲存空間是 1 Byte。不難驗證，在 32 位元系統的大多數實現裡，`int` 類型也正好是 32-bit 長度，可以取遍上述範圍。

同理，64 位元系統取值範圍為 0~264-1，`int` 類型長度為 64-bit。

## 使用指標的目的

### 簡化程式碼

如果沒有指標，很難用一個統一的模式去 `A` 的定位並修改一棵樹的節點。例如：不用指標要修改 `A` 的左子樹的左子樹的右子節點，只有「`A.LC.LC.RC=…`」一種表達方式，不能透過賦值而簡化。

### 參數傳遞

C 中函式調用是按值傳遞的，傳入參數在子函式中只是一個初值相等的副本，無法對傳入參數作任何改動。但實際程式設計中，經常要改動傳入參數的值。在 C 語言中一般透過傳入參數的位址而不是原參數本身來實現。當對傳入參數（位址）取「`*`」運算時，就可以直接在記憶體中修改，從而改動原想作為傳入參數的參數值。

**傳指標**

在執行 `inc(&a);` 時，操作 `*val`，即是在操作 `a` 了。

**傳值**

以下例子中，`main()` 內的變數從來沒有改變，改變的只是 `sw()` 內的變數。

 
`sw()` 執行完畢後，其內容會自動刪除。

## 指標的運算和聲明

### 取位址和取值運算

取值運算（`*p`）返回儲存在記憶體位址為 `p` 的記憶體空間中的值。取位址（`&p`）運算則返回運算元 `p` 的記憶體位址。顯然可以用賦值語句對記憶體位址賦值。

假設一段記憶體位址空間取值如下：（十六進位）

然後，執行代碼「`int *p;`」，假設初始化時 `p` 被分配 3001H、3002H 兩個位址。則 `p` 為 2003H，`*p` 為 3000H。

則 `**p`、`&&p`、`*(&p)` 和 `&(*p)` 的值分別為：

`**p=*(*(p))=*(*(2003H))=*(3000H)=`0020H。

`&&p=&(&(p))=&(3001H)`，出錯，3001H 是常數，無位址可言。

`*&p=*(&(p))=*(3001H)`=2003H，也即 `*&p=p`。

`&*p=&(*(p))=&(3000H)`，出錯，3000H 是常數，無位址可言。

## 指標的複雜形式

### 雙重指標（指向指標的指標）

雙重指標是指向指標的指標，它是一個指標，這個指標指向某個記憶體位址，該位址的值是一個指標，指向給另一個記憶體位址（通常異於前者，但不排除二者相等）。

本質上，指標值就是記憶體位址。但為了防範指標值被濫用（如記憶體存取時越界），可以規定指標類型為強型別，即指標值及儲存在該記憶體位址的物件的類型。雙重指標不過是這種強型別的一個應用：該位址空間長度為一個指標長度（4 或 8 位元組），物件類型為另一種指標。

### 指標陣列

指標陣列：就是一個陣列，陣列的各個元素都是指標值。

### 陣列指標

陣列名出現在表達式中時，絕大多數情況（除了陣列名作為 sizeof 的運算元或者作為取位址 & 元素符的運算元）會被隱式轉換為指向陣列的首個元素的指標右值。

當陣列名作為取位址 & 運算子的運算元，則表達式的值為指向整個陣列的指標右值。

例子：

根據上述 C 語言標準中的規定，表達式 &s 的值的類型是 char (\*)[6]，即指向整個陣列的指標；而表達式 s 則被隱式轉換為指向陣列首元素的指標值，即 char\* 類型。同理，表達式 s [4] ，等效於表達式 \*(s+4)。

### 指向函式的指標

指向函式的指標：不同於指向資料類型的指標，函式指標指向一段可執行的代碼的首位址，這段代碼仍然占用了一塊記憶體空間。很多人都說 C 語言是一種程序導向的語言，因為它最多只有結構體的定義，而沒有類的概念。根據本段所述，可以認為 C 語言能成為物件導向的語言，只是表述比較麻煩而已。事實上很多開源程式都使用這種方式組織他們的代碼。

### 指標運算子的多載

## 指標的進化與取代

由於指標太活躍，因此導致它幾乎能不受限制的在各種記憶體位址間活動，所以一旦有任何重複、重疊、溢位的情形發生時，電腦便直接當機，這成為指標功能上的最大缺憾。因此在新的網路程式語言的開發上，新的語言如 Java、C# 等語種已經取消了指標的無限制使用形式。 C# 允許指標的有限功能的使用，指標和運算指標在一個操作的環境中是存在潛在的非安全性的，因為他們的使用可以避開物件的一些嚴格存取規則。C# 中使用指標的代碼段或者方法的位址要用 unsafe 關鍵字進行標記，這樣，這些代碼的使用者就會知道這個代碼相比其他的代碼而言是不具有安全性的。編譯器需要 unsafe 關鍵字時將使用此代碼的程式轉換成是允許被編譯的。一般來說，不安全代碼的使用可能是為了非代管的 API（應用程式編程介面）的更好互用，或者是為了（存在內在不安全性的）系統調用，也有可能是出於提高效能等方面的原因。而 Java 中不允許指標或者算術指標的使用。

## 參考