void 型指针的高阶用法,你掌握了吗?
[導讀] 要比較靈活的使用C語言實現一些高層級的框架時,需要掌握一些進階編程技巧,這篇來談談void指針的一些妙用。測試環境采用 IAR for ARM 8.40.1
推薦一首中文歌曲<<后來>>,英文翻唱<<life>>
來自瑞典歌手Sofia Kallgern:
什么是void指針
void指針一般被稱為通用指針或叫泛指針。它是C語言關于純粹地址的一種約定。當某個指針是void型指針時,所指向的對象不屬于任何類型。 因為void指針不屬于任何類型,則不可以對其進行算術運算,比如自增,編譯器不知道其自增需要增加多少。比如char *型指針,自增一定是指針指向的地址加1,short *型指針自增,則偏移2。
在C/C++中,在任意時刻都可以使用其它類型指針來代替void指針,或者用void指針來代替其他類型指針。
由這些特性就可以衍生出很多比較有用的技巧。指針的本質,是其值為一個地址,那么延伸一下:
當使用關鍵字void聲明指針變量時,它將成為通用指針變量。任何數據類型(char,int,float等)的任何變量的地址都可以賦值給void指針變量。
對指針變量的解引用,使用間接運算符*達到目的。但是在使用空指針的情況下,需要轉換指針變量以解引用。這是因為空指針沒有與之關聯的數據類型。編譯器無法知道void指針指向的數據類型。因此,要獲取由void指針指向的數據,需要使用在void指針位置內保存的正確類型的數據進行類型轉換。
對于空指針的解引用,你如不信,就來看看栗子:
看到了吧,直接解引用編譯不過,因為編譯器蒙了。
但須注意的是:
不同的編譯器對void指針處理是不一樣的,如IAR,ANSI C,VC對上述都將出錯,而GNU指定“void”的算法操作與“char”一致,因此上述寫法在GNU則可以編譯
所以做個類型轉換,修正如下:
void型指針解引用須做類型指定。
類型轉換的時候須注意類型匹配。
另外,如果函數類型可以是任意類型的指針,則需將其參數定義為void *指針,例如string.h中關于內存操作的函數集:
??__EFF_NENW1NW2???__ATTRIBUTES???int???????memcmp(const?void?*,?const?void?*,size_t);__EFF_NENR1NW2R1?__DEPREC_ATTRS?void?*????memcpy(void?*_Restrict,const?void?*_Restrict,size_t);__EFF_NENR1NW2R1?__DEPREC_ATTRS?void?*????memmove(void?*,?const?void?*,size_t);__EFF_NENR1R1????__DEPREC_ATTRS?void?*????memset(void?*,?int,?size_t);非易失存儲管理應用
在單片機開發中,往往需要實現數據的非易失存儲。所謂非易失存儲,就是數據改寫后在掉電后仍然能保持。哪些是非易失存儲介質呢?比如EEPROM,FLASH等都屬于非易失存儲介質。
比如一個產品里面有很多各種各樣的參數,且分布在各個子系統文件中。舉個栗子:
/*模塊A中有這樣一個結構體需要非易失存儲*/ typedef?struct?_t_paras{int?language;/*語言種類*/char?SN[20];?/*產品序列號*/ }T_PARAS; T_PARAS?sysParas;/*模塊B中有這樣一個結構體需要非易失存儲*/ typedef?struct?_t_pid{float?kp;float?ki;float?kd;float?T; }T_PID; T_PID?pidParas;面對這樣一個需求,要實現非易失存儲,我在將底層的EEPROM/FLASH讀寫函數實現的基礎上,將上述應用數據按照一定順序存儲管理。那么更為理想的方式是什么呢?設計一個模塊專門負責存儲非易失數據。比如:
typedef?struct?_t_nv_layout{void?*?pElement;?/*參數地址*/int????length;???/*參數長度*/ }T_NV_LAYOUT; /*參數映射表*/ T_NV_LAYOUT?nvLayout[]={{&sysParas,sizeof(T_PARAS)},/*參數映射記錄*/{&pidParas,sizeof(T_PID)},... }; /*參數映射表記錄條數*/ #define?NV_RECORD_NUMBER??(sizeof(nvLayout)/sizeof(T_NV_LAYOUT)) void?nv_load(T_NV_LAYOUT?*pLayout,int?nvAddr,int?number); void?nv_store(T_NV_LAYOUT?*pLayout,int?nvAddr,int?number);將上述設計思想,利用UML描述一下:
在上述基礎上,我們只需要設計硬件層抽象,即可設計出一個可行的、比較通用的NV管理子系統,這樣設計出的子系統忽略了業務數據,僅僅將其處理為數據,并不關心其業務意義。實現了業務邏輯與后臺的隔離解耦。做到了通用性。這里就比較巧妙的利用了void *指針的特性。如果對于該設計思想,在進一步延伸,將底層的抽象在做一層封裝,將更細節的底層實現細節隔離抽象,比如:
抽象I2C/SPI EEPROM,將其對上層的調用接口統一,那么如果你的系統原本是存儲在I2C EEPROM中,現在做一個新項目,你需要使用另外一種SPI接口的EEPROM,則只需要實現相應的底層處理函數即可。
將存儲介質抽象,比如是EEPROM/DATA FLASH等...
....
那么怎么做到底層抽象呢,我們可以利用函數指針定義統一的接口,具體部署時,只需要將實現函數的指針賦值給對應的函數指針即可,這樣就做到了接口的抽象統一。其實這就是驅動模型的一個簡易雛形。
總結一下
這篇文章引入了一些編程思想,對于單片機/嵌入式進階編程比較有用:
利用void *指針,將業務數據與底層存儲實現了抽象解耦
利用分層抽象實現了代碼具有良好的可移植性
利用函數指針實現了C++等高級語言的虛函數定義接口的思想
統一接口底層實現抽象,實現了驅動分層的思想
void *指針由這個例子,可以延伸出很多類似的應用
啟示:一些語言細節如果深入了解其背后的機理,可以得到很多比較巧妙的應用。
留言區
—END—
如果喜歡右下點個在看,也會讓我倍感鼓舞
往期精彩推薦,點擊即可閱讀
?
推薦閱讀:
專輯|Linux文章匯總
專輯|程序人生
專輯|C語言
我的知識小密圈
關注公眾號,后臺回復「1024」獲取學習資料網盤鏈接。
歡迎點贊,關注,轉發,在看,您的每一次鼓勵,我都將銘記于心~
嵌入式Linux
微信掃描二維碼,關注我的公眾號
總結
以上是生活随笔為你收集整理的void 型指针的高阶用法,你掌握了吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微处理器:50岁了!
- 下一篇: 单片机(MCU)如何才能不死机之对齐访问