聯合體(union)的使用方法及其本質

原地址:http://blog.csdn.net/huqinwei987/article/details/23597091

1.聯合體union的基本特性——和struct的同與不同

union,中文名“聯合體、共用體”,在某種程度上類似結構體struct的一種數據結構,共用體(union)和結構體(struct)同樣可以包含很多種數據類型和變量。

不過區別也挺明顯:

結構體(struct)中所有變量是“共存”的——優點是“有容乃大”,全面;缺點是struct內存空間的分配是粗放的,不管用不用,全分配。

而聯合體(union)中是各變量是“互斥”的——缺點就是不夠“包容”;但優點是內存使用更爲精細靈活,也節省了內存空間。


2.雙刃劍——多種訪問內存途徑共存

一個例子瞭然:

//example  
#include<stdio.h>  
union var{  
        long int l;  
        int i;  
};  
main(){  
        union var v;  
        v.l = 5;  
        printf("v.l is %d\n",v.i);  
        v.i = 6;  
        printf("now v.l is %ld! the address is %p\n",v.l,&v.l);  
        printf("now v.i is %d! the address is %p\n",v.i,&v.i);  
}  
結果:  
v.l is 5  
now v.l is 6! the address is 0xbfad1e2c  
now v.i is 6! the address is 0xbfad1e2c

所以說,管union的叫共用體還真是貼切——完全就是共用一個內存首地址,並且各種變量名都可以同時使用,操作也是共同生效。如此多的access內存手段,確實好用,不過這些“手段”之間卻沒法互相屏蔽——就好像數組+下標和指針+偏移一樣。

上例中我改了v.i的值,結果v.l也能讀取,那麼也許我還以爲v.l是我想要的值呢,因爲上邊提到了union的內存首地址肯定是相同的,那麼還有一種情況和上邊類似:

一個int數組變量a,一個long int(32位機中,long int佔4字節,與int相同)變量b,我即使沒給int變量b賦值,因爲數據類型相同,我使用int變量b也完全會拿出int數組a中的a[0]來,一些時候一不小心用上,還以爲用的就是變量b呢~

這種邏輯上的錯誤是很難找出來的(只有當數據類型相去甚遠的時候稍好,出個亂碼什麼的很容易發現錯誤)。


PS:感謝熱心網友的提醒“在union定義結束時加分號”,其實是可以不加的,因爲他不在主函數內,不是執行的語句,如果是主函數內聲明的union就必須加分號了,在主函數內不加分號就涉及到基礎常識了——沒有分號隔開怎能叫一句。


3.聯合體union和大小端(big-endian、little-endian):


#include<stdio.h>  
union var{  
        char c[4];  
        int i;  
};  
  
int main(){  
        union var data;  
        data.c[0] = 0x04;//因爲是char類型,數字不要太大,算算ascii的範圍~  
        data.c[1] = 0x03;//寫成16進製爲了方便直接打印內存中的值對比  
        data.c[2] = 0x02;  
        data.c[3] = 0x11;  
//數組中下標低的,地址也低,按地址從低到高,內存內容依次爲:04,03,02,11。總共四字節!  
//而把四個字節作爲一個整體(不分類型,直接打印十六進制),應該從內存高地址到低地址看,0x11020304,低位04放在低地址上。  
        printf("%x\n",data.i);  
}

結果:
11020304
證明我的32位linux是小端(little-endian)


4.聯合體union所佔內存空間大小:

前邊說了,首先,union的首地址是固定的,那麼,union到底總共有多大?根據一些小常識,做個不嚴謹不高深的基礎版驗證吧。

根據:分配棧空間的時候內存地址基本上是連續的,至少同類型能保證在一起,連續就說明,我如果弄三個結構體出來,他們三個地址應該連着,看一下三個地址的間隔就知道了。

#include<stdio.h>  
union sizeTest{  
        int a;  
        double b;  
};  
main(){  
        union sizeTest unionA;  
        union sizeTest unionB;  
        union sizeTest unionC;  
  
        printf("the initial address of unionA is %p\n",&unionA);  
        printf("the initial address of unionB is %p\n",&unionB);  
        printf("the initial address of unionC is %p\n",&unionC);  
}



打印,可以看到結果:

the initial address of unionA is 0xbf9b8df8
the initial address of unionB is 0xbf9b8e00
the initial address of unionC is 0xbf9b8e08

很容易看出,8,0,8,這間隔是8字節,按double走的。

怕不保險,再改一下,把int改成數組,其他不變:

union sizeTest{  
        int a[10];  
        double b;  
};


打印

the initial address of unionA is 0xbfbb7738
the initial address of unionB is 0xbfbb7760
the initial address of unionC is 0xbfbb7788

88-60=28
60-38=28
算錯了?我說的可是16進制0x。那麼0x28就是40個字節,正好是數組a的大小。


似乎忘了一個功能——sizeof()

用sizeof直接看,就知道union的大小了

        printf("the sizeof   of unionA is %d\n",sizeof(unionA));  
        printf("the sizeof   of unionB is %d\n",sizeof(unionB));  
        printf("the sizeof   of unionC is %d\n",sizeof(unionC));  
        printf("the sizeof   of union is %d\n",sizeof(union sizeTest));


5.聯合體union適用場合:

有了前邊那個驗證,基本可以確認,union的內存是照着裏邊佔地兒最大的那個變量分的。

也就可以大膽的推測一下,這種union的使用場合,是各數據類型各變量佔用空間差不多並且對各變量同時使用要求不高的場合(單從內存使用上,我覺得沒錯)。

像上邊做的第二個測試,一個數組(或者更大的數組int a[100]),和一個或者幾個小變量寫在一個union裏,實在沒什麼必要,節省的空間太有限了,還增加了一些風險(最少有前邊提到的邏輯上的風險)。所以,從內存佔用分析,這種情況不如直接struct。


不過話說回來,某些情況下雖然不是很節約內存空間,但是union的複用性優勢依然存在啊,比如方便多命名,這種“二義性”,從某些方面也可能是優勢。這種方法還有個好處,就是某些寄存器或通道大小有限制的情況下,可以分多次搬運。



6.本質&進階:


根據union固定首地址union按最大需求開闢一段內存空間兩個特徵,可以發現,所有表面的定義都是虛的,所謂聯合體union,就是在內存給你劃了一個足夠用的空間,至於你怎麼玩~它不管~!(何止是union和struct,C不就是玩地址麼,所以使用C靈活,也容易犯錯)


沒錯,union的成員變量是相當於開闢了幾個接口(即union包含的變量)!但是,沒開闢就不能用了?當然也能用!

寫個小測試:

#include<stdio.h>  
union u{  
        int i;  
        double d;//這個union有8字節大小  
};  
main(){  
        union u uu;  
        uu.i = 10;  
        printf("%d\n",uu.i);  
  
        char * c;  
        c = (char *)&uu;//把union的首地址賦值、強轉成char類型  
        c[0] = 'a';  
        c[1] = 'b';  
        c[2] = 'c';  
        c[3] = '\0';  
        c[4] = 'd';  
        c[5] = 'e';  
//最多能到c[7]  
        printf("%s\n",c);//利用結束符'\0'打印字符串"abc"  
        printf("%c %c %c %c %c %c\n",c[0],c[1],c[2],c[3],c[4],c[5]);  
}

一個例子瞭然,我的結構體只定義了int和double“接口”,只要我獲得地址,往裏邊扔什麼數據誰管得到?這就是C語言的強大,這就是union的本質——只管開闢一段空間。

有些東西,熟悉編譯原理和編譯器工作過程的話,解決會更容易點,雖然我現在這方面技能不太強,不過一般問題也足夠分析了。


====================================================================================================================================


補充:


補充1:

解決一下捧場網友的困惑。

關於“有名”與“無名”聯合體在結構體內所佔空間的問題,其實這和是不是結構體無關,只和“有名”、“無名”有關,而且有名無名也是表象,其實是聲明類型與定義變量的區別,看例子,直接打印,


#include <stdio.h>  
struct s1{  
        union u{  
                int i;  
        };  
        struct ss1{  
                int i;  
        };  
};  
  
struct s2{  
        union{  
                int i;  
        };  
        struct{  
                int i;  
        };  
};  
  
struct s3{//the same to s2  
        union su3{  
                int i;  
        }su33;  
        struct ss3{  
                int i;  
        }ss33;  
};  
  
union su4{  
        int i;  
};  
struct ss4{  
        int i;  
};  
struct s4{//the same to s3  
        union su4 su44;  
        struct ss4 ss44;  
};  
struct s5{//the same to s1  
        union su4;  
        struct ss4;  
};  
  
struct s6{//the same to s1  
        union{  
                int;  
        };  
        struct{  
                int;  
        };  
};  
  
main(){  
        struct s1 sVal1;  
        struct s2 sVal2;  
        struct s3 sVal3;  
        struct s4 sVal4;  
        struct s5 sVal5;  
        struct s6 sVal6;  
  
        printf("sVal1's size:%d\n",sizeof(sVal1));  
        printf("sVal1:%p\t%d\n",&sVal1);  
  
        printf("sVal2's size:%d\n",sizeof(sVal2));  
        printf("sVal2:%p\t%d\n",&sVal2);  
  
        printf("sVal3's size:%d\n",sizeof(sVal3));  
        printf("sVal3:%p\t%d\n",&sVal3);  
  
        printf("sVal4's size:%d\n",sizeof(sVal4));  
        printf("sVal4:%p\t%d\n",&sVal4);  
  
        printf("sVal5's size:%d\n",sizeof(sVal5));  
        printf("sVal5:%p\t%d\n",&sVal5);  
  
        printf("sVal5's size:%d\n",sizeof(sVal5));  
        printf("sVal5:%p\t%d\n",&sVal5);  
}

地址供參考,主要看size,分別爲:

0,8,8,8,0,0


s1只有類型,沒有變量,沒有變量自然就沒有空間(s5同)。

類型就是類型,和是不是結構體、聯合體無關的,你的“int i;”中i不就是個變量嗎?如果換成int;結果相同(這就是s6)。


s4和s5的做法能幫助排除干擾,將子結構體與聯合體聲明在外,內部直接引用,4是定義了變量,5什麼都沒做。


另外,這種做法編譯的時候GCC會給你在相應的行做出提示“union_with_name.c:49: 警告:沒有聲明任何東西”


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章