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


union 本質是 共用一塊內存
可以吧 8bit 當 char 或者 int 這么理解

轉自: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定義結束時加分號”,因為最近不關注C,所以我把我的“不加分號也行”改成嚴謹的“只聲明一個union的時候不加也行”,至於為什么C語言有這種不一致的判定,我現在真不關心這種瑣碎。

#include<stdio.h>
union var{
    long int l;
    int i;
};
main(){
    union var v;
    v.l = 5;
    printf("%ld\n",v.l);
    v.i = 6;
}

3.聯合體union和大小端(big-endian、little-endian):
下邊示范了一種用途,代表四個含義的四個變量,但是可以用一個int來操作,直接int賦值,無論內存訪問(指針大小的整數倍,訪問才有效率),還是時間復雜度(一次和四次的區別,而且這四次有三次都是不整齊的地址),都會低一些。


#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)的本質——只管開辟一段空間。
但是你獲取地址並訪問和存取的數據,最好確定是合法(語法)合理(用途符合)的地址,不然雖然能操作,后患無窮,C的頭疼之處,可能出了問題你都找不到。

補充:
補充1:
解決一下捧場網友的困惑。
關於“有名”與“無名”聯合體在結構體內所占空間的問題,其實這和是不是結構體無關,只和“有名”、“無名”有關,而且有名無名也是表象,其實是聲明類型與定義變量的區別,看例子,直接打印,

#include <stdio.h>
struct s1{
        union u{
                int i;
        };
        struct ss1{
                int i;
        };
};
 
struct s2{
        union{
                int i;
        };
        struct{
                int i2;
        };
};
 
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,sVal1);
 
        printf("sVal2's size:%d\n",sizeof(sVal2));
        printf("sVal2:%p\t%d\n",&sVal2,sVal2);
 
        printf("sVal3's size:%d\n",sizeof(sVal3));
        printf("sVal3:%p\t%d\n",&sVal3,sVal3);
 
        printf("sVal4's size:%d\n",sizeof(sVal4));
        printf("sVal4:%p\t%d\n",&sVal4,sVal4);
 
        printf("sVal5's size:%d\n",sizeof(sVal5));
        printf("sVal5:%p\t%d\n",&sVal5,sVal5);
 
        printf("sVal6's size:%d\n",sizeof(sVal6));
        printf("sVal6:%p\t%d\n",&sVal6,sVal6);
}

地址供參考,主要看size,分別為:
0,8,8,8,0,0
s1只有類型聲明,沒有定義,沒有變量自然就沒有空間占用(s5同)。
s2這種寫法就是直接定了union和struct。
s3和s2的區別,只是s2過於簡化,s3的意思是既聲明了union su3,又定義了這個類型對應的變量su33.
s4和s5作為對比,為了更好的說明這一點。s5也是純“貼”表達式,沒聲明變量。
s6乍一看類似s2,其實union內部沒有具體變量,也是為了做對比的。和s1的不同之處是,一個是外部的union,一個是內部的int,都是干聲明不定義,所以沒成員,不占用空間。
類型就是類型,和是不是結構體、聯合體無關的,你的“int i;”中i不就是個變量嗎?如果換成int;結果相同(這就是s6)。
另外,這種做法編譯的時候GCC會給你在相應的行做出提示“union_with_name.c:49: 警告:沒有聲明任何東西”
很多人表示打印結果不一樣,我試過很多次,不一樣的環境,都是一樣的。

以上僅屬於個人心得和推測,重點在於學習思維和推理驗證過程,不保證正確性與權威性。有興趣討論或者有發現錯誤的,歡迎留言交流指正。
覺得好的,歡迎轉載並注明出處。
http://blog.csdn.net/huqinwei987/article/details/23597091
本人博客會根據個人經驗升級情況隨時補充修改。

https://blog.csdn.net/qq_38813056/article/details/85244969
(union, struct) 相互嵌套使用----技巧(你肯定沒見過)
1.語法示例
跳轉到的地方

點擊跳轉


#include <stdlib.h>
#include <stdio.h>
 
// 參考 Glibc庫源碼 sig_info.h
typedef struct info{
	int age;
	union {
		int code;
		struct {
			int pid;
			int uid;
		} id;
		struct {
			int KM;
			char *addr_name;
		} address;
	} u;
} info_t;
 
#define code 		u.code
#define pid 		u.id.pid
#define uid			u.id.uid
#define KM  		u.address.KM
#define addr_name   u.address.addr_name
 
int main(int argc, char **argv)
{
	info_t info;
	
	info.age 	= 20;
	info.code   = 5;
	
	printf("age = %d\n", info.age);			// 20
	printf("code = %d\n", info.code);		// 5
	
	printf("----------------------\n");
	info.pid = 123;
	printf("age = %d\n", info.age);			// 20
	printf("code = %d\n", info.code);		// 123, 因為code變量與id變量共用一塊內存
	printf("pid = %d\n", info.pid);			// 123
 
	printf("----------------------\n");
	info.KM = 789;
	printf("age = %d\n",  info.age);		// 20
	printf("code = %d\n", info.code);		// 789, 因為code變量與address變量共用一塊內存
	printf("pid = %d\n",  info.pid);		// 789, 因為id變量與address變量共用一塊內存
	printf("KM = %d\n",   info.KM);			// 789
	return 0;
}

執行結果:

book@gui_hua_shu:~/test$ gcc union_struct.c
book@gui_hua_shu:~/test$ ./a.out
age = 20
code = 5
----------------------
age = 20
code = 123
pid = 123
----------------------
age = 20
code = 789
pid = 789
KM = 789
book@gui_hua_shu:~/test$

https://blog.csdn.net/keyearth/article/details/6129882

struct stExample
{
    char    a;
    char    b
    short   c;
    int     d;
}; //
sizeof( char ) == 1
sizeof( short ) == 2
sizeof( int ) == 4
/
sizeof( struct stExample ) == 8

-------------------------
struct stExample
{
    char    a;
    int     b;
    short   c;
}; //
sizeof( char ) == 1
sizeof( short ) == 2
sizeof( int ) == 4
/
sizeof( struct stExample ) == 12  //?
-------------------------

struct stExample
{
    char    a;
    int     b;
    short   c;
}; //
sizeof( char ) == 1
sizeof( short ) == 2
sizeof( int ) == 4
/
sizeof( struct stExample ) == 12  //?
--------------------------
#pragma pack (2) /*指定按2字節對齊*/
struct stExample
{
    char   a;
    int    b;
    short  c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
///
sizeof( struct stExample ) == 8 
------------------------
struct stExample
{
    char    a;
    struct  b
    {
        char    aa;
        short   bb;
        int     cc;
    }
    short   c;
};
//
sizeof( struct stExample ) == 16 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM