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

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

轉自:https://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呢~

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

#include<stdio.h>
union var{long int l;int i;
};
int 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 s2union su3{int i;}su33;struct ss3{int i;}ss33;
};union su4{int i;
};
struct ss4{int i;
};
struct s4{//the same to s3union su4 su44;struct ss4 ss44;
};
struct s5{//the same to s1union su4;struct ss4;
};struct s6{//the same to s1union{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://www.pswp.cn/news/532792.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532792.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532792.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux設備驅動之串口移植,Linux設備驅動之UART驅動結構

一、對于串口驅動Linux系統中UART驅動屬于終端設備驅動&#xff0c;應該說是實現串口驅動和終端驅動來實現串口終端設備的驅動。要了解串口終端的驅動在Linux系統的結構就先要了解終端設備驅動在Linux系統中的結構體系&#xff0c;一方面自己了解的不夠&#xff0c;另一發面關于…

linux python復制安裝,復制一個Python全部環境到另一個環境,python另一個,導出此環境下安裝的包...

復制一個Python全部環境到另一個環境&#xff0c;python另一個,導出此環境下安裝的包導出此環境下安裝的包的版本信息清單pipfreeze>requirements.txt聯網&#xff0c;下載清單中的包到all-packet文件夾[[email protected] ~]# pip download -d ./all-packet -r requirement…

NVIDIA英偉達的Multi-GPU多卡通信框架NCCL

NVIDIA英偉達的Multi-GPU多卡通信框架NCCL 筆者注&#xff1a;NCCL 開源項目地址&#xff1a;https://github.com/NVIDIA/nccl 轉自&#xff1a;https://www.zhihu.com/question/63219175/answer/206697974 NCCL是Nvidia Collective multi-GPU Communication Library的簡稱&…

C語言n個坐標點間的最大距離,c語言已知兩點坐標,求另一點到穿過這兩點的直線最短距離。...

c語言已知兩點坐標&#xff0c;求另一點到穿過這兩點的直線最短距離。以下文字資料是由(歷史新知網www.lishixinzhi.com)小編為大家搜集整理后發布的內容&#xff0c;讓我們趕快一起來看一下吧&#xff01;c語言已知兩點坐標&#xff0c;求另一點到穿過這兩點的直線最短距離。#…

[分布式訓練] 單機多卡的正確打開方式:理論基礎

[分布式訓練] 單機多卡的正確打開方式&#xff1a;理論基礎 轉自&#xff1a;https://fyubang.com/2019/07/08/distributed-training/ 瓦礫由于最近bert-large用的比較多&#xff0c;踩了很多分布式訓練的坑&#xff0c;加上在TensorFlow和PyTorch之間更換&#xff0c;算是熟…

s3c2416開發板 linux,S3C2416移植內核Linux3.1的wm9713聲卡過程

移植內核的聲卡驅動。原因沒有聲卡驅動&#xff0c;WM9713聲卡驅動移植(原來的內核有UDA1341聲卡驅動&#xff0c;我們再次基礎上直接修改)1、直接復制內核得到三個文件:s3c2416_wm9713.c , wm9713.c , s3c2416_ac97.c.linux-3.1\sound\soc\codecs\Wm9713.c---->wm9713.c;li…

Linux查看文件內容命令:cat, tail, head, more, less

Linux查看文件內容命令&#xff1a;cat, tail, head, more, less cat 直接顯示整個文件。 cat直接顯示全部文件內容&#xff0c;沒有換頁等交互。 cat filenamemore more命令&#xff0c;功能類似 cat &#xff0c;cat命令是整個文件的內容從上到下顯示在屏幕上。 more會…

linux查看隊列 msg,linux第10天 msg消息隊列

cat /proc/sys/kernel/msgmax最大消息長度限制cat /proc/sys/kernel/msgmnb消息隊列總的字節數cat /proc/sys/kernel/msgmni消息條目數消息隊列綜合案例//server#include #include #include #include #include #include #include #include #define ERR_EXIT(m)do{perror(m);}wh…

Linux中 C++ main函數參數argc和argv含義及用法

Linux中 C main函數參數argc和argv含義及用法 簡介 argc 是 argument count的縮寫&#xff0c;表示傳入main函數的參數個數&#xff1b; argv 是 argument vector的縮寫&#xff0c;表示傳入main函數的參數序列或指針&#xff0c;并且第一個參數argv[0]一定是程序的名稱&…

c語言六位搶答器課程設計,51單片機八路搶答器課程設計

;說明&#xff1a;本人的這個設計改進后解決了前一個版本中1號搶答優先的問題&#xff0c;并增加了錦囊的設置&#xff0c;當參賽選手在回答問題時要求使用錦囊&#xff0c;則主持人按下搶答開始鍵&#xff0c;計時重新開始。;八路搶答器電路請看下圖是用ps仿真的&#xff0c;已…

ELF文件詳解—初步認識

ELF文件詳解—初步認識 轉自&#xff1a;https://blog.csdn.net/daide2012/article/details/73065204 一、 引言 在講解ELF文件格式之前&#xff0c;我們來回顧一下&#xff0c;一個用C語言編寫的高級語言程序是從編寫到打包、再到編譯執行的基本過程&#xff0c;我們知道在C…

埃及分數問題c語言,埃及分數問題(轉)

今日&#xff0c;小雨和小明來到網絡中心&#xff0c;繼續與劉老師討論“數的認識”問題。劉老師說&#xff1a;“還有一種‘埃及分數’需要認識。這是一類分裂分數的思維題&#xff0c;對思維能力的訓練很有價值。”小明說&#xff1a;“有意思&#xff0c;愿洗耳恭聽。”劉老…

linux常用命令--開發調試篇

前言 Linux常用命令中有一些命令可以在開發或調試過程中起到很好的幫助作用&#xff0c;有些可以幫助了解或優化我們的程序&#xff0c;有些可以幫我們定位疑難問題。本文將簡單介紹一下這些命令。 轉自&#xff1a;https://www.yanbinghu.com/2018/09/26/61877.html 示例程序…

簡單有趣的c語言小程序,一個有趣的小程序

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓源碼:#include #include #include #include #include HINSTANCE g_hInstance 0;LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nSh…

linux下ora 01110,ORA-01003ORA-01110

Oracle 9i數據庫登錄時&#xff0c;提示ORA-01003&ORA-01110&#xff0c;大概意思是數據文件存儲介質損壞。startup nomount,正常&#xff1b;alter database mount,也正常&#xff1b;alter database open,提示如下&#xff1a;alter database open*ERROR 位于第 1 行:ORA…

x11轉發:通過ssh遠程使用GUI程序

x11轉發&#xff1a;通過ssh遠程使用GUI程序 我們常常使用ssh服務遠程操控服務器&#xff0c;大多數操作我們都可以通過命令行命令來實現。 ssh遠程無法查看GUI程序 現在&#xff0c;筆者在x11-test目錄下放入一張圖片test.jpg&#xff0c;并通過opnencv-python寫一個簡單的…

操作系統引導詳細過程

操作系統引導詳細過程 轉自&#xff1a;https://blog.csdn.net/lijie45655/article/details/89366372 就直觀而言&#xff0c;我們所見到計算機啟動的過程是&#xff1a;按下電腦開機鍵&#xff0c;系統在黑色的屏幕下打印出一些英文語句、然后進入進度條狀態&#xff0c;最后…

android 自定義透明 等待 dialog,Android自定義Dialog內部透明、外部遮罩效果

Android自定義Dialog內部透明、外部遮罩效果發布時間&#xff1a;2020-09-09 03:01:41來源&#xff1a;腳本之家閱讀&#xff1a;117作者&#xff1a;zst1303939801本文實例為大家分享了Android自定義Dialog遮罩效果的具體代碼&#xff0c;供大家參考&#xff0c;具體內容如下圖…

對比損失的PyTorch實現詳解

對比損失的PyTorch實現詳解 本文以SiT代碼中對比損失的實現為例作介紹。 論文&#xff1a;https://arxiv.org/abs/2104.03602 代碼&#xff1a;https://github.com/Sara-Ahmed/SiT 對比損失簡介 作為一種經典的自監督損失&#xff0c;對比損失就是對一張原圖像做不同的圖像…

android 融云瀏覽大圖,融云 Android sdk kit 頭像昵稱更新機制

先申明筆者的實現方式不是唯一 也不一定是最優化的方案 如果您看到此篇博文 有不同看法 或者 更好的優化 更高的效率 歡迎在評論發表意見 融云官網點我融云頭像機制相關視頻詳解首先跟大家說一下 kit 跟 lib 的頭像機制 kit 是已經包含融云已經給開發者定制好的界面 諸如 會話界…