漫談C指針(1):指針背後的邏輯

標題圖: commons.wikimedia.org/w

In the beginning, you always want results.

In the end, all you want is control.

-- How I program C, Eskil Steenberg

什麼是指針?你也許會覺得這是一個簡單的問題,每個學過C語言的人都應該知道什麼是指針。然而對於C語言開發者來說,安全地使用指針並不是一個簡單的問題。在C語言中指針是和邪惡的,指針引起混亂,使程序崩潰,導致不確定結果而且還可能導致內存泄露,很多人避之不及。但是它又是完全有必要的,作為一種對於硬體間接定址的抽象,指針又是C語言中不可缺少的一部分。指針就是這樣一個邪惡的存在,雖然程序員厭惡它,卻又離不開它。但是究竟為什麼指針會是一種邪惡的東西?問題出在哪裡?我們真正了解指針嗎?

如何讓指針變得安全?一種最直接的方法就是在語言中完全乾掉指針,Java 和 Python都是典型的例子。高層的語言機制和引用概念完美地代替了指針,從而把程序員從指針的邪惡中解救出來。但是,這種方案並不是對所有人都有效。例如很難想像一個操作系統設計者能夠使用一種完全沒間接定址概念的語言來實現內存管理,在很多時候,指針作為一種對硬體的抽象是有必要的。

如果你編程的目的是為了得到結果,讓指針變得更安全的最好的方法是不要用帶指針的語言。但是如果編程的目的是控制硬體,那麼接下來我們一起來探討一下指針罪惡的根源。

所以,

究竟什麼是指針?

你可能會說,指針就是一個整數用來表示一個內存地址,用來表示對內存中其他位置的引用。但是,「引用」二字能夠完全概括我們想要的語義嗎?

讓我們先來看兩個例子

==

例1:動態數組

假如我們需要實現一個動態整數數組類型

typedef struct { size_t capacity; size_t size; int* data;} integer_array_t;

那麼對於一個 const interger_array_t* 類型的變數ptr來說,ptr->data的類型應該是什麼?

了解C語言的人都知道,答案是int *, const 關鍵字並不會傳遞給它的成員。但是從邏輯上講,這並不合理,因為如果我們傳入const integer_array_t* 是希望這個數組本身不被修改。

void func(const integer_array_t* arr){ arr->data[0] ++; // C 是允許的,但是從邏輯上講我們不希望這種事情發生}

例2:緩衝區描述符

假設我們需要實現一個類型來描述一個緩衝區的參數

typedef struct { char* memory; size_t length;} buffer_desc_t;

而後我們實現一個向緩衝區內寫入數據的函數

void write_buffer(unsigned offset, char byte, const buffer_desc_t* buffer){ //這裡略過邊界檢查 buffer->memory[offset] = byte; // C依然允許,而且這就是我們期望的結果}

為什麼我們需要一個const buffer_desc_t* 呢?因為我們並不希望這個函數去修改這個buffer的定義,我們僅僅希望它修改buffer。

==

在例1和例2中,我們可以發現,雖然同樣是一個指針成員,但是我們期望的行為完全相反。那麼指針從邏輯上講是一個統一的概念嗎?

事實上例一和例二的區別在於,例一中指針指向的內存是整個動態數組的一部分,那麼const

qualifier必然應該作用於這片內存。例二則相反,緩衝區描述符僅僅表示write_buffer函數需要寫內存中的這個位置,而指向的那篇內存事實上並不是描述符的一部分。

換一個角度來看,例一中的指針和對應的內存是一對一的關係,不可能存在兩個數組指向同一片內存。然而在例二中,描述符可能有很多個,它們都可以指向同一片內存。

所以,同樣是指針,它們背後的邏輯卻完全不同。指針其實並不是一個定義良好的概念,C指針的語法並不能完全概括我們需要的語義。在很多情況下,這就是指針引起問題的原因。

回到例一,由於data指針表示了data 指向的內存是整個數組的一部分,所以在integer_array_t 釋放的時候,data指針必然也要在同一時間釋放,否則會內存泄漏。

對於例二,由於memory指針僅僅表示一個首地址,所以在buffer_desc_t釋放的時候memory並不需要被釋放,否則有可能double free 或者memory指向BSS段導致segsev。

這兩種錯誤的原因都歸結於,我們混淆了表示擁有關係的指針以及單純表示地址的指針。這兩種指針的維護方式完全不同。指針背後究竟有多少種不同的邏輯,它們的行為都是什麼樣的?

這些內容我們下回再更。

======================================================

漫談C指針(2):指針的分類 - 知乎專欄


推薦閱讀:

TAG:C編程語言 | C指針 | 編程 |