本頁使用了標題或全文手工轉換

指標 (電腦科學)

維基百科,自由的百科全書
跳至導覽 跳至搜尋
名為 a 的指標,指向一個記憶體位址,當中的值為 b。要注意的是,在這個示意狀況中使用的計算結構,對指標及非指標,都使用相同的位址空間以及表示法,但是在真實狀況中,不同的計算結構可能有不同做法。

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

歷史[編輯]

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

概論[編輯]

在計算機科學中,指標是一種最簡單形式的參照(reference)。

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

指標作為資料類型,可以從一個函式類型、一個物件類型或者一個不完備類型中匯出。從中匯出的資料類型稱之為被參照類型(referenced type)。指標類型描述了一類的物件,物件值為對被參照類型的實體的參照。[1]

C++標準中規定,「指標」概念不適用於成員指標(不包含指向靜態成員的指標)。[2]C++標準規定,指標分為兩類:[3]

  • 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一般避免用指標,改為使用參照[4]

指標的實質[編輯]

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

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

32位元系統的定址能力(位址空間)是4GB[6](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語言中一般通過傳入參數的位址而不是原參數本身來實現。當對傳入參數(位址)取「*」運算時,就可以直接在記憶體中修改,從而改動原想作為傳入參數的參數值。

傳指標
#include <stdio.h>

void inc(int *val){
    (*val)++;
}

int main(){
    int a=3;
    inc(&a);
    printf("%d\n", a);
    return 0;
}

// Output:
// 4

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

傳值

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

#include <iostream>
using namespace std;

void sw(int x, int y) {
	int Temp;
	Temp = x;
	x = y;
	y = Temp;
}

int main() {
	int a=1;
	int b=2;
	cout <<  a << b << endl;
	sw(a,b);
	cout <<  a << b << endl;
	return 0;
}

// Output:
// 12
// 12


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

a b x y
1 2 - -
1 2 1 2
1 2 2 1
1 2 - -

指標的運算和聲明[編輯]

取位址和取值運算[編輯]

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

假設一段記憶體位址空間取值如下:(十六進位)

位址 0000 2000 2001 2002 2003 2004 3000 3001 3002 3003
取值 ???? 01 30 00 30 00 00 20 03 9A

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

**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的運算元或者作為取位址&元素符的運算元)會被隱式轉換為指向陣列的首個元素的指標右值

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

例子:

char s[]="hello";

int main() {
  char (*p1)[6]=&s; //OK!
  char (*p2)[6]=s; //compile error: cannot convert 'char*' to 'char (*)[6]'   
  char *p3=&s;//compile error: cannot convert 'char (*)[6]' to 'char*' 
}

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

指向函式的指標[編輯]

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

#include <stdio.h>

void inc(int *val)
{
    (*val)++;
}

int main(void)
{
    void (*fun)(int *);
    int a=3;
    fun=inc;
    (*fun)(&a);
    printf("%d", a);
    return 0;
}

指標運算子的多載[編輯]

指標的進化與取代[編輯]

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

參考[編輯]

  1. ^ C99語言標準的6.2.5 Types中規定:A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called 『『pointer to T』』. The construction of a pointer type from a referenced type is called 『『pointer type derivation』』.
  2. ^ C++11標準3.9.2 Compound types中的規定:Except for pointers to static members, text referring to 「pointers」 does not apply to pointers to members
  3. ^ C++11標準3.9.2第3段
  4. ^ 實質上Java在傳遞物件的時候用的是按指標(這裡認為指標和參照沒有本質區別)傳遞,在傳遞基本類型(如int)時用的是按值(副本)傳遞。
  5. ^ C++11標準3.7.4.3
  6. ^ 這和32位元作業系統最大支援記憶體大小沒有關係,所謂32位元作業系統只支援4GB的說法是不對的,Windows 2003資料中心版的32位元版本就支援最大64GB的記憶體。作業系統支援的記憶體數還取決於其儲存存取的組織形式,以及作業系統的使用許可。
  7. ^ C99標準6.5.3.2.3中規定:The unary & operator returns the address of its operand.
  8. ^ 這裡的位元組序為小尾序,Little-Endian,低位在低位址,Intelx86系列CPU適用。MotorolaPowerPC系列則採用大尾序,Big-Endian,高位在低位址,則上述p為0320H,*p需進一步查記憶體0320H處的儲存值。
  9. ^ 實際上,可以用C語言完成COM模組,這完全類比了C++語言的類、物件、虛表、虛擬函式等結構。