1. 程式人生 > >union理解與妙用

union理解與妙用

最近在看PCL中關於自定義PointT型別的文件,發現其中廣泛使用了union,以前學習的時候用不到這個,也就沒有留心,藉此機會學習下union的使用方法,理解下union的本質。

一 struct和union記憶體結構

我們先來簡單看下struct的記憶體結構,如下定義一個結構體:

struct student
{
    char mark;
    long num;
    float score;
};

由於記憶體對齊的存在,這個student結構體的記憶體結構如下:
這裡寫圖片描述
雖然mark理論上只有一個位元組,但是卻會佔用4個位元組的空間。
下面定義一個與student資料成員一樣的union:

union union_student
{
    char mark;
    long num;
    float score;
};

我們發現sizeof(union_student)的值為4,這就是union和struct的區別了。有時候,我們需要幾種不同型別的變數存在在同一段的記憶體空間中,就像上面的,我們需要將一個char型別的mark、一個long型別的num變數和一個float型別的score變數存放在同一個地址開始的記憶體單元中。上面的三個變數,char型別和long型別所佔的記憶體位元組數是不一樣的,但是在union中,它們都是從同一個地址存放的,也就是使用的覆蓋技術,這三個變數互相覆蓋,而這種使幾個不同的變數共佔同一段記憶體的結構,稱為“共用體”型別的結構,也稱“聯合體”,也就是我們要說的union了,記憶體結構如下:
這裡寫圖片描述


由上面,我們大致可以看出:結構體struct所佔用的記憶體為各個成員的佔用的記憶體之和(當然也需要考慮記憶體對齊的問題了)。而對於union來說,union變數所佔用的記憶體長度等於最長的成員的記憶體長度,同樣需要考慮記憶體對齊的問題。

二 union的使用

和struct一樣,union只有先定義了共用體變數才能引用它。而且不能直接引用共用體變數,而只能引用共用體變數中的成員。
同時,在使用union的時候,還需要注意以下的幾點:
1.同一個記憶體段可以用來存放幾種不同型別的成員,但在每一個時刻只能存在其中一種,而不是同時存放幾種。也就是說,每一瞬間只有一個成員起作用,其它的成員不起作用,即不是同時都存在和起作用。
2.共用體變數中起作用的成員是最後一個存放的成員,在存入一個新的成員後,原有的成員就失去作用。比如以下的程式碼:

union_student a;

// cout<<a<<endl; // wrong
a.mark = 'b';
cout << a.mark << endl; // 輸出'b'
cout << a.num << endl; // 98 字元'b'的ACSII值
cout << a.score << endl; // 輸出錯誤值

a.num = 10;
cout << a.mark << endl; // 輸出空
cout << a.num << endl; // 輸出10
cout << a.score << endl; // 輸出錯誤值

a.score = 10.0;
cout << a.mark << endl; // 輸出空
cout << a.num << endl; // 輸出錯誤值
cout << a.score << endl; // 輸出10

3.由於union中的所有成員起始地址都是一樣的,所以&a.mark、&a.num和&a.score的值都是一樣的。
4.union型別可以出現在結構體型別定義中,也可以定義union陣列,反之,結構體也可以出現在union型別定義中,陣列也可以作為union的成員。

三 union + 物件

當union遇到了C++中的物件時,一切又變得複雜起來。上面總結的union使用法則,在C++中依然適用。本來union本就是從C語言中的,如果我們在C++中繼續按照C語言的那種方式使用union,那是沒有問題的。如果我們在union中放一個類的物件呢?結果會怎麼樣?比如有以下程式碼:

#include <iostream>
using namespace std;

class CA
{
     int m_a;
};

union Test
{
     CA a;
     double d;
};

int main()
{
     return 0;
}

可以看到,沒有問題;如果我們在再類CA中添加了建構函式,或者新增解構函式,我們就會發現程式就會出現錯誤。由於union裡面的東西共享記憶體,所以不能定義靜態、引用型別的變數。由於在union裡也不允許存放帶有建構函式、解構函式和複製建構函式等的類的物件,但是可以存放對應的類物件指標。編譯器無法保證類的建構函式和解構函式得到正確的呼叫,由此,就可能出現記憶體洩漏。所以,我們在C++中使用union時,儘量保持C語言中使用union的風格,儘量不要讓union帶有物件。

四 理解PointT

我們先看一段程式碼:

#define PCL_ADD_UNION_NORMAL4D \
  union EIGEN_ALIGN16 { \
    float data_n[4]; \
    float normal[3]; \
    struct { \
      float normal_x; \
      float normal_y; \
      float normal_z; \
    }; \
  };

這是PCL中定義normal(法向量)的原始碼。有了上面的知識,我們在來理解這個應該就沒有問題了,其中:

  • data_n[4] 為了記憶體對齊設計的
  • normal[3] 和struct 裡面的內容是主角,normal[3]方便我們進行整體的操作,而struct裡面的內容則有助於我們訪問normal中的各個分量,雖然直接normal[i]也可以,但是沒有normal_x,normal_y,normal_z直觀。這樣的定義也有助於我們更好的理解程式碼。