我的C實踐(2):聯合的妙用

來源:互聯網
上載者:User

    在C語言中,等位型別是一種比較特殊的類型,其多個成員共用一個儲存區(為最大成員的長度),一次只能包含一個成員值,會進行記憶體對齊。對等位型別進行sizeof運算會包括所有成員所需要的儲存空間量,還包括成員間和成員後面的填充空間。聯合類似於其他語言中的“變體記錄”,如果聯合的長度很大或者有大量的聯合,則可以大大節省儲存空間。
    1、聯合只能一次賦值一個成員,並使用它,但C語言沒有提供查詢聯合上一次賦值所用成員的方法。我們可以定義一個enum,其中各個枚舉常量代表union各個成員的標誌。然後把union和這個enum封裝在一個struct中,當對union的某個成員賦值時,就設定相應的標誌,這樣就可以跟蹤聯合的成員賦值。

/* widget.c:用枚舉常量來跟蹤等位型別的成員賦值 */<br />#include <stdio.h><br />#include <string.h><br />enum widget_tag{ /* 聯合的各個成員的標誌 */<br />count_widget,<br />value_widget,<br />name_widget<br />};<br />struct WIDGET{ /* 把union和enum封裝在一個struct中 */<br />enum widget_tag tag;<br />union{<br />long count;<br />double value;<br />char name[10];<br />} data;<br />} x;<br />typedef struct WIDGET widget;<br />/* 調用本函數就可以不必考慮聯合的上一次賦值所用成員 */<br />void print_widget(widget w){<br />switch(w.tag){<br />case count_widget:<br />printf("Count: %ld/n",w.data.count);<br />break;<br />case value_widget:<br />printf("Value: %.15f/n",w.data.value);<br />break;<br />case name_widget:<br />printf("Name: /"%s/"/n",w.data.name);<br />break;<br />}<br />}<br />int main(){<br />x.tag=count_widget; /* 要給成員count賦值,設定其標誌 */<br />x.data.count=10000;<br />print_widget(x); /* 會自動列印該成員的值 */</p><p>x.tag=value_widget;<br />x.data.value=3.14159265358979323846;<br />print_widget(x);</p><p>x.tag=name_widget;<br />strncpy(x.data.name,"Millard",10);<br />print_widget(x);<br />return 0;<br />}<br />

    2、如果引用聯合的某一個成員,但聯合上一次賦值的不是該成員,則是以不可移植的方式使用聯合。我們可以利用這個方法來瞭解電腦內部的一些底層資料表示方法。例如要發現浮點數是如何表示的:

#include <stdio.h><br />/* 用等位型別來擷取浮點數的底層表示 */<br />void print_rep(float f){</p><p>union{ /* 用相同長度的浮點數和整數產生一個聯合 */<br />float a;<br />int i;<br />} aori; </p><p>aori.a=f; /* 對浮點數成員賦值 */<br />/* 讀取整數成員值,由於它們共用儲存空間,因此可以得到浮點數的記憶體表示 */<br />printf("The representation of %12.7e is %#010x/n",aori.a,aori.i);<br />}<br />int main(){<br />print_rep(16.5); /* 擷取16.5的記憶體表示 */<br />printf("%#010x/n",(int)16.5); /* 直接轉換則不能擷取記憶體表示 */<br />return 0;<br />}

    float型的儲存布局一般是1位符號位S,8位指數E,23位尾數。轉換成數值V=(-1)^S*1.M*2^(E-127)。例如16.5=00010000.1=1.00001*2^4,則符號位為0,指數位為4+127=131=10000011,尾數為00001000000000000000000,拼接起來即得到16.5的記憶體表示,與代碼中的結果一致。注意這種方法並不一定完全可移植。如果把浮點數直接轉型成整數,則不能發現底層表示,因為(int)16.5會直接截掉尾部,轉換成整數16。
    但我們可以用間接轉型的方式來擷取浮點數的底層表示,例如地址轉型
(只轉型對象的記憶體位址,而不轉型對象記憶體中的內容),使用地址轉型是一種比較通用的用來擷取任意類型對象記憶體表示的方法。這裡把浮點數變數的地址轉換成unsigned char*型指標c,這樣指標c就存放了浮點數變數的起始地址,即*c裡面就是f的低位位元組的內容,*(c+1)~*(c+3)為其他位元組的內容,注意*(c+i)等價於c[i],因此*(c+i)也可換成c[i]。

#include <stdio.h><br />/* 用類型轉換來擷取浮點數的底層表示 */<br />int main(){<br /> float f = 16.5;<br />int i = 0;<br />/* c存放f的記憶體起始地址,*c裡就是f的低位位元組的內容 */<br /> unsigned char* c = (unsigned char *)&f;<br /> /* 列印f的4個位元組中的內容,從高位位元組到低位位元組 */<br /> for (i = 3; i >= 0; i--)<br /> printf("0x%x/n", *(c+i)); /* 也可用c[i] */<br /> return 0;<br />}

    3、不同的電腦體繫結構有不同的位元組儲存順序。一種是低位儲存法(即小端位元組序),如Intel 80x86,從右至左儲存,高地址位元組中儲存整數的高位,字的地址是最右邊(低位)位元組的地址。一種是高位儲存法(即大端位元組序,TCP/IP網路傳輸採用大端位元組序),如Motorola 680x0,從左至右儲存,高地址位元組中儲存整數的低位,字的地址是最左邊(高位)位元組的地址。我們可以用聯合來確定電腦的位元組順序。

#include <stdio.h><br />/* 用等位型別來確定電腦的位元組儲存順序 */<br />union{<br />long ma;<br />char mb[sizeof(long)];<br />} u;<br />int main(){<br />u.ma=1; /* 初始化後ma的低位位元組中包含1 */</p><p>if(u.mb[0]==1) /* ma的低位位元組與mb[0]重疊,說明從右至左儲存 */<br />printf("Addressing is right-to-left/n");<br />else if(u.mb[sizeof(long)-1]==1) /* 從左至右儲存 */<br />printf("Addressing is left-to-right/n");<br />else printf("Addressing is strange/n");<br />return 0;<br />}

 

    u的長度與long類型相同(例如為4個位元組),初始化後1在ma的低位中,高位中全為0,mb[0]~mb[3]的地址從低到高。在從右至左的體繫結構中,ma的低位1儲存在低地址位元組中,因此與mb[0]重疊。在從左至右的體繫結構中,ma的低位1儲存在高地址位元組中,因此與mb[3]重疊。由此可確定電腦的位元組儲存順序。當然,我們也可以用地址轉型的方式來判斷。如下:

#include <stdio.h><br />/* 用地址轉型來確定電腦的位元組儲存順序 */<br />int main(){<br />long f=1;<br />/* c存放f的記憶體起始地址,*c裡就是f的低位位元組的內容 */<br /> unsigned char* c = (unsigned char*)&f;<br /> /* 若*c為1,說明是從右至左儲存,若*(c+3)為1,說明是從左至右儲存 */<br /> if(*c==1)<br /> printf("Addressing is right-to-left/n");<br /> else if(*(c+3)==1)<br /> printf("Addressing is left-to-right/n");<br /> else<br /> printf("Addressing is strange/n");</p><p> return 0;<br />}

    不同體繫結構的位元組序如下:

處理器                             作業系統                   位元組排序
Alpha                              全部                            Little endian
HP-PA                             NT                              Little endian
HP-PA                             UNIX                          Big endian
Intel x86                         全部                             Little endian(x86系統是小端位元組序系統)
Motorola 680x0            全部                             Big endian
MIPS                               NT                               Little endian
MIPS                               UNIX                           Big endian
PowerPC                        NT                               Little endian
PowerPC                       非NT                            Big endian  (PowerPC是大端位元組序)
RS/6000                         UNIX                           Big endian
SPARC                           UNIX                           Big endian(SPARC是大端位元組序)
IXP1200 ARM核心       全部                             Little endian

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.