union用法 - c++符號




C和C++中的聯合的目的 (10)

其他人提到了架構差異(小 - 大端)。

我讀到了這樣的問題,即由於變量的內存是共享的,那麼通過寫入一個變量,其他變量就會改變,並且根據它們的類型,這個值可能沒有意義。

例如。 union {float f; int i; } X;

如果從xf讀取數據,寫入xi將毫無意義 - 除非您想要查看浮點數的符號,指數或尾數部分。

我認為還有一個對齊問題:如果一些變量必須是字對齊的,那麼你可能得不到預期的結果。

例如。 union {char c [4]; int i; } X;

假設在某些機器上一個字符必須進行字對齊,那麼c [0]和c [1]將與i共享存儲空間,而不是c [2]和c [3]。

我早些時候很舒服地使用過工會; 今天,當我閱讀這篇文章並且知道這段代碼時,我感到非常驚慌

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

實際上是未定義的行為,即從最近寫入的聯盟成員讀取會導致未定義的行為。 如果這不是工會的預期用法,那是什麼? 有人可以詳細解釋嗎?

更新:

我想事後澄清幾件事。

  • 對於C和C ++來說,這個問題的答案並不相同; 我的無知年輕的自我標記為C和C ++。
  • 通過C ++ 11標準的搜索後,我無法確定地說它調用訪問/檢查非活動聯合成員是未定義的/未指定的/實現定義的。 我能找到的所有內容都是§9.5/ 1:

    如果標準佈局聯合包含多個共享初始序列的標準佈局結構,並且此標準佈局聯合類型的對象包含其中一個標準佈局結構,則允許檢查任何公共初始序列的標準佈局結構成員。 §9.2/ 19:如果相應的成員具有佈局兼容類型,並且兩個標準佈局結構都共享一個共同的初始序列,並且這兩個成員都不是位域,或者都是一個或多個初始序列的具有相同寬度的位域成員。

  • 在C中( C99 TC3 - DR 283起),這樣做是合法的( 感謝Pascal Cuoq提出這一點)。 但是,如果讀取的值發生無效(所謂的“陷阱表示”),則試圖執行此操作仍然會導致未定義的行為 。 否則,讀取的值是實現定義的。
  • C89 / 90在未指明行為的情況下(附件J)對此進行了說明,K&R的書中說明了它的實施定義。 來自K&R的報價:

    這是一個聯合的目的 - 一個單一的變量,可以合法地持有幾種類型的任何一種。 只要使用一致:檢索的類型必須是最近存儲的類型。 程序員有責任跟踪當前存儲在工會中的類型; 如果某個東西被存儲為一個類型並被提取為另一個類型,則結果與實現相關。

  • 從Stroustrup的TC ++ PL中提取(重點是我的)

    對於“類型轉換有時被濫用的數據的兼容性而言,使用聯合可能是必不可少的。

最重要的是,這個問題(自從我的問題以來,其標題保持不變)的目的是理解工會的目的,而不是標准允許的內容。例如,當然,C ++標准允許使用繼承進行代碼重用,但將繼承作為C ++語言功能引入的目的或初衷並非如此 。 這就是安德烈的答案繼續保持為接受的原因。


在1974年所記錄的C語言中,所有結構成員都共享一個通用名稱空間,“ptr-> member”的含義被定義為將成員的位移添加到“ptr”並使用成員的類型訪問得到的地址。 這種設計使得可以使用相同的ptr,其成員名稱取自不同的結構定義,但具有相同的偏移量; 程序員將這種能力用於各種目的。

當結構成員被賦予它們自己的名稱空間時,就不可能用相同的位移聲明兩個結構成員。 在語言中添加聯合可以實現語言的早期版本中可用的相同語義(儘管無法將名稱導出到封閉上下文中仍可能需要使用查找/替換來替換foo-> member進入foo-> type1.member)。 重要的不是加入工會的人有任何特定的目標用法,而是他們提供了一種方法,依靠先前語義的程序員為了任何目的仍應該能夠實現即使他們必須使用不同的語法來完成相同的語義。


在C中,這是實現類似變體的好方法。

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

在litlle內存的時候,這個結構比擁有所有成員的結構使用更少的內存。

順便說一下C提供的

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

訪問位值。


工會的目的相當明顯,但由於某種原因,人們常常錯過它。

聯合的目的是通過使用相同的內存區域在不同的時間存儲不同的對象來節省內存 而已。

它就像旅館裡的一個房間。 不同的人生活在非重疊的時間段內。 這些人永遠不會見面,而且一般都不了解對方。 通過恰當地管理房間分時(即確保不同的人不會同時分配到一個房間),​​相對較小的酒店可以為相對大量的人提供住宿,這是什麼酒店是給。

這正是聯盟所做的。 如果您知道程序中的多個對象具有不重複的值生命週期值,則可以將這些對象“合併”為一個聯合,從而節省內存。 就像酒店房間在每個時刻最多只有一個“活躍”租戶一樣,工會在每個節目時間最多只有一個“活躍”會員。 只有“活動”成員可以閱讀。 通過寫入其他成員,您可以將“活動”狀態切換到該其他成員。

出於某種原因,工會的這個最初目的被完全不同的東西“覆蓋”:寫一個工會的一個成員,然後通過另一個成員檢查它。 這種內存重新解釋(又名“類型雙關”) 不是工會的有效使用。 它通常導致未定義的行為被描述為在C89 / 90中產生實現定義的行為。

編輯:對於C99標準的技術勘誤之一(參見DR#257DR#283 ),使用工會用於類型剔除(即寫入一個成員然後再讀取另一個成員)的目的得到了更詳細的定義。 但是,請記住,正式情況下,這不會通過嘗試讀取陷阱表示來防止發生未定義的行為。


從語言的角度來看,這種行為是不確定的。 考慮到不同平台在內存對齊和字節順序上可能有不同的限制。 大端序與小序端機器中的代碼將不同地更新結構中的值。 修復語言中的行為將要求所有實現都使用相同的字節順序(和內存對齊限制...)來限制使用。

如果您使用的是C ++(您使用兩個標記),並且您真的關心可移植性,那麼您可以使用該結構並提供一個setter,它接受uint32_t並通過位掩碼操作適當地設置字段。 使用函數C也可以做到這一點。

編輯 :我期待AProgrammer寫下投票的答案並關閉這一個。 正如一些評論指出的那樣,通過讓每個實現決定做什麼,排列和填充也可以以不同的方式處理,從而在標準的其他部分處理了排序。 現在,AProgrammer暗示的嚴格別名規則在這裡很重要。 允許編譯器對變量的修改(或缺少修改)作出假設。 在聯合的情況下,編譯器可以重新排序指令並將每個顏色分量的讀取移到寫入顏色變量上。


您可以使用聯合來創建如下的結構,其中包含一個字段,用於告訴我們聯合的哪個組件實際使用:

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

有兩個主要原因可以使用聯盟:

  1. 一種方便的方式,以不同的方式訪問相同的數據,就像你的例子
  2. 當存在不同的數據成員時,一種節省空間的方法,其中只有一個數據成員可以“活躍”

1在您了解目標系統的內存架構如何工作的基礎上,實際上更多的是C編寫的快捷編寫代碼。 正如已經說過的,如果你實際上沒有針對很多不同的平台,你通常可以逃避它。 我相信一些編譯器可能會讓你使用包裝指令(我知道他們在結構上做的)?

2.可以在COM中廣泛使用的VARIANT類型中找到一個很好的例子。


有關實際使用聯合的更多示例,CORBA框架使用標記聯合方法序列化對象。 所有用戶定義的類都是一個(巨大的)聯合的成員, 整數標識符告訴demarshaller如何解釋聯合。


行為可能是未定義的,但這只意味著沒有“標準”。 所有體面的編譯器都提供#pragmas來控制打包和對齊,但可能有不同的默認值。 默認值也會根據使用的優化設置而變化。

此外,工會不僅僅是為了節省空間。 他們可以幫助現代編譯器打字。 如果你reinterpret_cast<> ,編譯器無法對你正在做什麼做出假設。 它可能不得不拋棄它對類型的了解並重新開始(迫使寫回內存,與CPU時鐘速度相比,這種效率非常低效)。


雖然這是嚴格未定義的行為,但實際上它可以與幾乎任何編譯器一起工作。 這是一個廣泛使用的範例,任何自我尊重的編譯器都需要在諸如此類的情況下做“正確的事情”。 它肯定比類型雙關更受歡迎,這可能會導致一些編譯器產生破壞的代碼。







type-punning