union 在中文的叫法中又被稱爲共用體,聯合或者聯合體,它定義的方式與 struct 是相同的,可是意義卻與 struct 徹底不一樣,下面是 union 的定義格式:數據結構
union 共用體名 { 成員列表 }共用體變量名;
那麼它與結構體的定義方式相同,那麼區別是什麼呢,下面經過一個 struct 與 union 的嵌套來講明二者的區別所在。函數
struct my_struct { int type; union my_union { char *str; int number; }value; }Elem_t;
訪問方式是同結構體是同樣的,好比我要訪問 number 變量,那麼就能夠以以下的方式進行訪問:學習
Elem_t.value.number = 10;
union 與 struct 的區別是什麼呢?用一句話歸納就是共用體中的成員的地址都是同樣的,結構體中的成員都具備各自的地址,下面用一張圖展現 Elem_t 在內存中的存儲。
看到變量在內存中的存儲位置以後,也就明白 union 的特性了,對於這樣存儲的好處顯而易見,程序中可以使用不一樣類型的變量而且只佔用一個變量的存儲空間,可以節省存儲空間。上述程序中共用體的中兩個成員所佔的存儲空間大小同樣,都是四個字節,因此最終這個共用體所佔存儲空間的大小就是四個字節,若是共用體的成員的存儲空間大小不同,那麼共用體存儲空間的大小取決於成員中存儲空間最大的一個。優化
在使用聯合在打包數據的時候,必需要清楚當前處理器是大端對齊仍是小端對齊。ui
下面用圖的形式舉一個例子分別在大端對齊和小端對齊中的存儲形式。
有了大端對齊和小端對齊的認知下,咱們來看 union 如何對數據進行打包,下面給出一段代碼:3d
#include <stdio.h> int main(void) { union { unsigned int word; struct { unsigned char byte1; unsigned char byte2; }byte; }u1; u1.byte.byte1 = 0x21; u1.byte.byte2 = 0x43; printf("The Value of word is:0x%x\n",u1.word); }
上述的運行結果會根據對齊方式的不同而有所差異。
若是是小端模式:code
The Value of word is:0x4321
若是是大端模式:blog
The Value of word is:0x2143
固然對於採用這種方式進行數據的打包來講,弊端也是很明顯的,由於會由於處理器的對齊方式而產生不一樣的結果,因此,咱們每每採用的都是經過數據移位的方式來實現:圖片
uint8_t byte3 = 0x21; uint8_t byte4 = 0x43; uint16_t word; word = (((uint16_t)byte4) << 8)|((uint16_t)byte3);
上述的寫法便不會收處處理器對齊方式的影響,也具備更好地移植性。內存
背景:如今有兩個小車須要進行通訊,分別是小車 A 和小車 B ,有些時候,小車 A 須要向小車 B 發送它當前的速度,有些時候,小車 A 須要向小車 B 發送它當前的位置,而有些時候小車 A 須要向小車 B 發送它當前的狀態。
分析:在上面的背景當中,咱們得知發送的消息的時候並非同時要發送速度,狀態,位置,而是這三個參數分開來的,並非同時須要,那這個時候,咱們就能夠採用 union 的特性來構造一個數據結構,這樣作的好處是可以縮減變量佔用的內存,好比說咱們不採用 union 來構造的話,一般咱們會採用結構體的方式,好比這樣:
struct buffer { uint8_t power; /*當前電池容量*/ uint8_t op_mode; /*操做模式*/ uint8_t temp; /*當前的溫度*/ uint16_t x_pos; uint16_t y_pos; uint16_t vel; /*小車當前的速度*/ }my_buff;
採用上述的結構的話,咱們能夠計算一下(不考慮內存對齊的狀況,內存對齊的話要對結構體內存進行填充,筆者打算後面單寫一篇文章記錄內存對齊的問題),結構體佔用的存儲空間是 9 個字節,爲了優化咱們的代碼,咱們能夠採用以下的方式來構造咱們要傳輸的數據。
union { struct { uint8_t power; uint8_t op_mode; uint8_t temp; }status; struct { uint16_t x_pos; uint16_t y_pos; }position; uint16_t vel; }msg_union;
這樣一來,從存儲空間來說,這個 union 所佔的空間只有 4 個字節。
若是要將發送的數據封裝成一個數據幀,那上面所定義的 union 就存在問題了,由於接收方就不知道發送方發過去的是哪一個參數,所以,須要在裏面加入參數類型這個變量,因而就有了以下的代碼:
struct { uint8_t msg_type; union { struct { uint8_t power; uint8_t op_mode; uint8_t temp; }status; struct { uint16_t x_pos; uint16_t y_pos; }position; uint16_t vel; }msg_union; }message;
有了 msg_type 的加入,咱們就能夠在接收端對數據進行解析了。
經過上述的這個例子,咱們如今來回顧一下,若是不使用 union 的話,在進行數據傳輸的時候,直接將由 struct 構造的數據造成數據幀發送過去,發送的數據包要比使用 union 構造的數據大很多,使用 union 構造數據,既可以幫助咱們節省了存儲空間,還節省了通訊時的帶寬。
上面一個例子咱們使用 union 在數據傳輸中優化了代碼,那麼 union 在數據解析中又具備什麼做用呢,看下面這樣一段代碼:
typedef union { uint8_t buffer[PACKET_SIZE]; struct { uint8_t size; uint8_t CMD; uint8_t payload[PAYLOAD_SIZE]; uint8_t crc; }fields; }PACKET_t; // 函數調用方法: packet_builder(packet.buffer,new_data) // 將新數據存到 buffer 的時候,還須要一些額外的操做 // 好比應該將 size 存放 buffer[0]中 // 將 cmd 存放到 buffer[1] 中,依次類推 void packet_builder(uint8_t *buffer,uint8_t data) { static uint8_t received_bytes = 0; buffer[received_bytes++] = data; } void packet_handler(PACKET_t *packet) { if (packet->fields.size > TOO_BIG) { //錯誤 } if (packet->fields.cmd == CMD) { //處理對應的數據 } }
要理解這個數據解析過程,須要用到 union 中的成員存放在同一個地址這個特性,buffer[PACKET_SIZE]中的元素與 fields 中的元素是一一對應的,用一張圖來表示就很清楚了,以下圖:
看了這張圖,我想就很清楚了,往 buffer 裏寫了數據,直接從 fileds 裏面讀出來就能夠了。
運用好 union 不只僅是可以節省存儲空間,用好地址共享這個特性也可以實現很精妙的效果,筆者以前都沒怎麼用過 union,這幾天關於 union 的學習也使筆者意識到路漫漫其修遠兮,可是也引用胡適先生的一句話:怕什麼真理無窮,進一寸有一寸的歡喜。
參考資料:
[1] https://www.allaboutcircuits.com/technical-articles/union-in-c-language-for-packing-and-unpacking-data/
[2] https://www.allaboutcircuits.com/technical-articles/learn-embedded-c-programming-language-understanding-union-data-object/.
[3] https://stackoverflow.com/questions/252552/why-do-we-need-c-unions.
最後,若是您以爲個人文章對您有幫助,歡迎添加個人我的公衆號:wenzi嵌入式軟件,期待與您一同前行~