C語言陷阱與技巧31節,都說void*指針是「萬能指針」,它萬能在哪

2020-12-05 IT劉小虎

在C語言程序開發中,一些比較成熟的庫函數常常會被使用。畢竟,如果手邊就有不錯的「輪子」可以用,沒有程式設計師願意再花費精力憑空造一個輪子出來。

沒有程式設計師願意憑空造輪子

奇怪的 void* 指針

事實上,C語言標準庫提供了非常豐富的成熟函數供程式設計師使用,不過不知道讀者注意到沒,有些庫函數的參數是 void * 類型的,例如:

void *memcpy(void *dest, constvoid *src, size_t n);void *memmove(void *dest, constvoid *src, size_t n);前面的章節在討論C語言指針時,提到指針從某種程度上來說,無非就是一個地址,它的類型只是用於說明數據結構的。例如 int 型指針告訴編譯器該地址處緊接著的 4 字節按照 int 型數據解釋,double 型指針高速編譯器接下來的 8 字節按照 double 型數據解釋。

這裡假定int型變量佔用4位元組內存空間,double 型變量佔用 8 字節內存空間。

對於 void 型指針,之前的分析似乎就不再適用了。因為 void 類型是一個特殊的類型,常被稱作「空類型」,C語言中沒有 void 類型的變量,所以在遇到 void 指針時,編譯器根本不知道如何解釋接下來的內存,甚至編譯器都不知道接下來多少內存屬於它

編譯器都不知道接下來多少內存屬於它

正因為如此,在C語言程序開發中,遇到 void * 指針時,如果需要訪問它指向的內存,需要重新指定類型,否則就會報錯。例如下面這段C語言代碼:

#include <stdio.h>void myprint(void *p){char c = p[0];printf("c=%c\n", c);}int main(){char buf[] = "hello world"; myprint(buf);return0;}void myprint(void *p){char c = ((char*)p)[0];printf("c=%c\n", c);}有時候為了簡便,常常使用中間變量:

void myprint(void *p){char *cp = (char*)p;char c = cp[0];printf("c=%c\n", c);}這時再編譯執行就一切正常了。

為什麼使用 void * 指針

僅從上面的實例來看,使用 void 指針似乎比較麻煩:至少強制類型轉換操作是少不了的。那為什麼還要使用 void 指針呢?

為什麼要是用void*指針

仔細分析上面的實例,讀者應注意「在使用 void 型指針時,要將其先轉換為 char 型」,這其實要求程式設計師事先了解 void 指針指向的數據結構(本例是 char 型),否則就沒法使用 void * 指針。

利用這一點,程式設計師可以使用 void * 將不公開的數據隱藏起來,避免外界調用者訪問和修改它。例如,在實際的C語言項目開發中,操作某個對象時,常常先構建結構體 struct S 描述該對象,然後使用 init() 函數獲取相應信息,因為接下來的操作函數 handle() 需知道要操作哪個對象,所以要使用 init() 函數返回的信息,C語言代碼似乎可以這麼寫:

struct S *p = init();handle(p);從上面兩行C語言代碼可以看出,其實外界調用可以不用關心 p 的具體數據結構。不過,如果這麼寫了,外界又的確可以隨意訪問 p 指向的數據結構,這樣就很危險了。事實上,我們完全可以將 p 轉換為 void * 指針:

void *p = init();handle(p);這樣一來,只有 init() 庫的開發者才能訪問 p 指向的內容,避免外界調用者「意外」或者「惡意」的修改,整個C語言程序就會穩定和安全很多了。

另外,void 指針在一些教科書裡被稱作「萬能指針」,這主要是因為任意指針都可以使用 void 指針傳遞,並且編譯器不會報出「類型不匹配」相關的警告。例如,要是將 myprint() 函數的參數類型修改為 int * 型,相關C語言代碼如下:

void myprint(int *p){char *cp = (char*)p;char c = cp[0];printf("c=%c\n", c);}int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);小結

本節拋磚引玉,主要討論了 void 指針在實際C語言項目開發中的特性和用途。void 指針可以將不希望被公開的數據結構隱藏,避免外界調用「意外」或者「惡意」的修改。另外,void * 指針作為「萬能指針」,可以傳遞任意類型的指針,而不引起「類型不匹配」相關的編譯警告。

點個讚再走吧

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦

相關焦點

  • C語言中的「不透明」指針是什麼,它有什麼用呢?
    雖說指針是C語言中比較複雜的語法,但是確實非常好用,因此我寫過不少文章討論C語言中的指針,相信對初學者理解指針有一定的幫助。事實上,的確有讀者私信我說看了這些文章,「總算不再畏懼指針了」。不過他同時也問了一個問題:C語言有「不透明指針(opaque pointer)」嗎?
  • C語言的指針,簡介
    指針,是一個表示變量或函數的地址的無符號整數。指針的字節數,與CPU的位數有關,32位機是4位元組,64位機是8位元組。與高級語言的long類型的大小是一致的。C語言的結構體的地址,也是結構體的第一個成員變量的地址。當然,類型是不一樣的,數值一樣。C語言一般把管理結構的結構體變量,嵌入到它要管的數據結構裡,而且一般會放在第一個位置。
  • void *指針的妙用
    在閱讀源碼的過程中,我發現很多的代碼中都採用了鍊表,鍊表的也是非常有意義的一種。有我們在C語言中使用的那種數據嵌套指針的方式。也有在linux中將鍊表作為一個單獨的對象,然後將這個對象嵌入到不同的對象中,然後根據container_of()得到對應的對象指針。這些方式都是常用的方式之一。在看uC/OS-II中我閱讀源碼時發現其中竟然很少有關於鍊表的操作。開始也沒有仔細的去分析原因,我甚至認為位圖的方式取代了鍊表。
  • 「懸空指針」和「野指針」究竟是什麼意思?
    本文轉載自【微信公眾號:strongerHuang,ID:strongerHuang】經微信公眾號授權轉載,如需轉載與原文作者聯繫提起C語言大部分開發者很自然就會想到指針二字,沒錯,作為C的核心和靈魂,它的地位咱們就不再贅述了,現在我們想跟大家講的是指針中的兩個特有名詞:「懸空指針
  • 第四篇:C語言中指針與字符串核心知識點梳理
    它又有什麼意義?這就涉及到本文要講到的第一個核心概念:指針。重點包括:指針處理一維數組、動態內存分配等。C語言的基本數據類型中有一個char的關鍵詞,可以存儲單個的字符。那麼,像漢字以及由多個字符組成的內容,又該如何存儲呢?
  • 奇怪的C語言代碼,在變量前加上(void)是什麼操作?有什麼用?
    C語言的語法極其簡潔,即使是初次接觸程式語言的初學者也能很快學完它的語法。不過,C語言也是一門「靈活得過了頭」的程式語言,對於很多初學者來說,編寫C語言程序就好像拿著一堆最基本的磚塊,要修建一座大廈一樣,茫然找不到方向。
  • 言C語言陷阱與技巧第21節,函數只能返回一個值嗎?有多個返回值怎麼...
    如今幾乎找不到只專注於一門程式語言的程式設計師了。大多數程式設計師在自己平時的工作和生活中,一般都使用不止一門程式語言,例如小編在工作中主要使用的是C語言,但是有時候驗證算法也會使用 matlab 和 python,在業餘做別的項目時還會用到 C#。
  • C語言,局部變量的指針,棧幀
    C/C++不能返回局部變量的指針,是一條重要的語法規則。至於為什麼,則不是那麼顯眼。局部變量,是分配在棧上的變量,隨著函數調用的返回而失效。函數調用結束之後,局部變量的指針,也就是野指針了,不能在函數外繼續使用。
  • 青少年信息學競賽Pascal語言:指針(十)
    合肥奧數網訊:合肥市青少年信息學競賽Pascal語言:指針   指針   指針是通過地址來訪問變量的一種特殊的數據類型,屬於動態的數據結構,它可以在需要時產生,用完後則又可以取消或回收,以減少佔用的內存空間。指針變量與其他類型的變量不同,它佔有的不是數據,而是地址。
  • C語言|文件指針、fopen()、fscanf()、fclose()
    這是一個簡單的文件系統fscanf()函數的功能是把磁碟文件數據讀出保存到變量(內存)每一個文件都有自己的FILE結構和文件緩衝區exit(0)是系統標準函數,作用是關閉所有打開的文件,並終止程序的執行。
  • C語言編程:以實例教你學指向函數的指針
    指針是C語言的精髓,對於初學者來講,指針是C語言語法學習中比較難的知識點,而這裡面指向函數的指針更是不太容易理解。下面給大家講下怎樣學習理解C語言中指向函數的指針及編程方法和使用例子。注意:這是一篇關於C語言編程的基礎語法內容,C語言大神請繞過。
  • 51單片機基礎剖析(基於C語言)
    在編寫C語言程序時,不是特別必要的地方一般不要使用全局變量,而應當儘可能的使用局部變量。因為局部變量只在使用它的時候,才為其分配內存單元,而全局變量在整個程序的執行過程中都要佔用內存單元,且當全局變量使用過多時,會降低程序的可讀性。
  • 深入理解void類型
    儘管如此,但還是有極少數系統設定RAM區從0地址開始,但指向有效變量的指針不會指向0地址。即使「代碼區」從0地址開始,但在任何情況下,0地址都不是C語言中任何函數的起始地址,因此指向有效函數地址的指針也不會指向0地址。
  • C語言的一些高級議題
    指針是C語言的靈魂,我們經常聽到這樣的說法,當我們初學C語言的時候,似乎覺得也沒有什麼,但是當你越來越深入的了解它,你就會發現C語言的強大有時甚至超乎你的想像。C語言作為一種相對較為底層的語言,在某些方面有著不可替代的優勢。
  • C語言陷阱與技巧第2節,使用inline函數可以提升程序效率,但是讓...
    打開 Linux 內核原始碼,會發現內核在定義C語言函數時,有很多都帶有 「inline」關鍵字,請看下圖,那麼這個關鍵字有什麼作用呢?inline 關鍵字的作用在C語言程序開發中,inline 一般用於定義函數,inline 函數也被稱作「內聯函數」,C99 和 GNU C 均支持內聯函數。那麼在C語言中,內聯函數和普通函數有什麼不同呢?
  • 在C語言中如何高效地複製和連接字符串?
    就目前而言,在編程領域中,C語言的運用非常之多,它兼顧了高級語言的彙編語言的優點,相較於其它程式語言具有較大優勢。這兩組函數都將字符從一個對象複製到另一個對象,並且都返回它們的第一個參數:指向目標對象的起始指針。這種返回值的方式是導致函數效率低下的一個原因,而這正是本文要探討的主題。本文中展示的示例代碼僅僅用於說明目的。它們可能包含細微的錯誤,不應該被視為最佳代碼實踐。
  • 定義只有一個數組成員的C語言結構體有什麼用?
    C語言代碼示例編譯並執行上述C語言代碼,得到如下輸出:# gcc t.c# ./a.outsizeof arr is 8可見,在函數 fun() 內部,sizeof(arr) 並不等於數組長度 16,而是等於 8(指針長度,我的機器是 64 位的,指針佔用內存空間為 8 個字節),這說明即使函數 fun() 的參數C語言代碼明確指定為 fun(char arr[16]),在函數內部,arr 還是退化成指針了。
  • 深入理解C語言
    導讀:Dennis Ritchie過世了,他發明了C語言,一個影響深遠並徹底改變世界的計算機語言。一門經歷40多年的到今天還長盛不訓的語言,今天很多語言都受到C的影響,C++,Java,C#,Perl,PHP,Javascript等等。但是,你對C了解嗎?相信你看過本站的《C語言的謎題》還有《誰說C語言很簡單?》。
  • C語言陷阱與技巧第15節,為什麼每調用一次函數,就需要一次if判斷...
    如果 malloc() 函數執行失敗,buf 會指向 NULL,此時 sprintf() 函數就會操作空指針,引發段錯誤(Segmentation fault)。C語言程序中的「段錯誤」出現時,通常不會有其他錯誤提示信息,這對於調試來說是比較難受的。不過在 Linux 中可以設置 core dumped,利用 gdb 等工具排查。不管如何,「段錯誤」都是相對來說比較難定位的錯誤。
  • C語言編程技巧:以實例跟我學動態數組的創建及使用方法
    問題提出在C語言編程中,對於普通數組的定義,如定義一個包含10個int類型元素的一維數組a,我們採用下面的方式:int a[10];這種方式定義的數組是靜態數組,其特點是定義方便,無需管理其內存的佔用情況,但其缺點是一旦定義後,其數組的長度就固定了,而不能動態的改變其大小