C++ Struct


一.為何

struct tagNode                  struct tagNode

{                         {

  ElemType data;                   ElemType data;

  struct tagNode LeftNode;             struct tagNode* LeftNode;

  struct tagNode RightNode;            struct tagNode* RightNode;

};//無法通過編譯                  };//正常通過編譯

C/C++采用靜態編譯模型。程序運行時,結構大小會在編譯后確定。程序要正確編譯,編譯器必須知道一個結構所占用的空間大小。除此之外還有一個邏輯方面的問題,在這種情況下,想想可以有LeftNode.LeftNode.LeftNode.LeftNode這類錯誤也稱為類和結構的遞歸定義錯誤

如果采用指針形式,指針的大小與機器的字長有關,不管是什么類型,編譯后指針的大小總是確定的,所以這種情況下不需要知道結構struct tagNode的確切定義。例如,在32位字長的CPU中,指針的長度為4字節。所以,如果采用指針的形式,struct tagNode的大小在其編譯后即可確定,為sizeof(int)+4+4.對於對象形式的struct tagNode定義,其長度在編譯后是無法確定的。

二.結構體對齊

C語言結構體對齊也是老生常談的話題了。基本上是面試題的必考題。結構體到底怎樣對齊?下面總結了對齊原則,在沒有#pragma pack宏的情況下:

原則1、普通數據成員對齊規則:第一個數據成員放在offset為0的地方,以后每個數據成員存儲的起始位置要從該成員大小的整數倍開始(比如int在32位機為4字節,則要從4的整數倍地址開始存儲)。

原則2、結構體成員對齊規則:如果一個結構里有某些結構體成員,則該結構體成員要從其內部最大元素大小的整數倍地址開始存儲。(struct a里存有struct b,b里有char,int,double等元素,那b應該從8的整數倍開始存儲。)

原則3、結構體大小對齊規則:結構體大小也就是sizeof的結果,必須是其內部成員中最大的對齊參數的整數倍,不足的要補齊。

補充一點,如果數組作為結構體成員,比如:char a[3]。它的對齊方式和分別寫3個char是一樣的,也就是說它還是按1個字節對齊。如果寫: typedef char Array3[3];Array3這種類型的對齊方式還是按1個字節對齊,而不是按它的長度3對齊。如果共用體作為結構體成員,則該共用體成員要從其內部最大元素大小的整數倍地址開始存儲。

還有一種對齊原則,和上面三條一致,可以參考着理解。

成員對齊有一個重要的條件,即每個成員按自己的方式對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這里默認是8字節)中較小的一個對齊。並且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節。

這三個原則具體怎樣理解呢?我們看下面幾個例子,通過實例來加深理解。

例1:struct {
              short a1;
              short a2;
              short a3;
       }A;

       struct{
              long a1;
              short a2;
      }B;

sizeof(A) = 6; 這個很好理解,三個short都為2。

sizeof(B) = 8; 這個比是不是比預想的大2個字節?long為4,short為2,整個為8,因為原則3。

例2:struct A{
               int a;
               char b;
               short c;
        };

        struct B{
               char b;
               int a;
               short c;
       };

sizeof(A) = 8; int為4,char為1,short為2,這里用到了原則1和原則3。

sizeof(B) = 12; 是否超出預想范圍?char為1,int為4,short為2,怎么會是12?還是原則1和原則3。

深究一下,為什么是這樣,我們可以看看內存里的布局情況。

                    a        b   c
A的內存布局:1111, 1*,11

                    b       a       c
B的內存布局:1***,1111,11**

其中星號*表示填充的字節。A中,b后面為何要補充一個字節?因為c為short,其起始位置要為2的倍數,就是原則1。c的后面沒有補充,因為b和c正好占用4個字節,整個A占用空間為4的倍數,也就是最大成員int類型的倍數,所以不用補充。B中,b是char為1,b后面補充了3個字節,因為a是int為4,根據原則1,起始位置要為4的倍數,所以b后面要補充3個字節。c后面補充兩個字節,根據原則3,整個B占用空間要為4的倍數,c后面不補充,整個B的空間為10,不符,所以要補充2個字節。

再看一個結構中含有結構成員的例子:

例3:struct A{
               int a;
               double b;
               float c;
       };

       struct B{
              char e[2];
              int f;
              double g;  
              short h;
              struct A i;
       };

sizeof(A) = 24; 這個比較好理解,int為4,double為8,float為4,總長為8的倍數,補齊,所以整個A為24。

sizeof(B) = 48; 看看B的內存布局。

                    e         f       g                h                      i 
B的內存布局:11* *,1111,11111111, 11 * * * * * *,1111* * * *, 11111111, 1111 * * * * 。 i其實就是A的內存布局。i的起始位置要為24的倍數,所以h后面要補齊。把B的內存布局弄清楚,有關結構體的對齊方式基本就算掌握了。

以上講的都是沒有#pragma pack宏的情況,如果有#pragma pack宏,對齊方式按照宏的定義來。比如上面的結構體前加#pragma pack(1),內存的布局就會完全改變。sizeof(A) = 16; sizeof(B) = 32;有了#pragma pack(1),內存不會再遵循原則1和原則3了,按1字節對齊。沒錯,這不是理想中的沒有內存對齊的世界嗎。

                    a       b               c
A的內存布局:1111,11111111,1111

                    e    f       g               h   i 
B的內存布局:11,1111,11111111,11,1111, 11111111, 1111 

 那#pragma pack(2)的結果又是多少呢?#pragma pack(4)呢?留給大家自己思考吧,相信沒有問題。

 還有一種常見的情況,結構體中含位域字段。位域成員不能單獨被取sizeof值。C99規定int、unsigned int和bool可以作為位域類型,但編譯器幾乎都對此作了擴展,允許其它類型類型的存在。

使用位域的主要目的是壓縮存儲,其大致規則為: 
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止; 
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數倍; 
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6采取不壓縮方式,Dev-C++采取壓縮方式; 
4) 如果位域字段之間穿插着非位域字段,則不進行壓縮; 
5) 整個結構體的總大小為最寬基本類型成員大小的整數倍。

還是讓我們來看看例子。

例4:struct A{ 
               char f1 : 3; 
               char f2 : 4; 
               char f3 : 5; 
       };

                    a      b          c
A的內存布局:111,1111 *,11111 * * *

位域類型為char,第1個字節僅能容納下f1和f2,所以f2被壓縮到第1個字節中,而f3只能從下一個字節開始。因此sizeof(A)的結果為2。

例5:struct B{ 
               char f1 : 3; 
               short f2 : 4; 
               char f3 : 5; 
       };

由於相鄰位域類型不同,在VC6中其sizeof為6,在Dev-C++中為2。

例6:struct C{ 
               char f1 : 3; 
               char f2; 
               char f3 : 5; 
       };

非位域字段穿插在其中,不會產生壓縮,在VC6和Dev-C++中得到的大小均為3。

考慮一個問題,為什么要設計內存對齊的處理方式呢?如果體系結構是不對齊的,成員將會一個挨一個存儲,顯然對齊更浪費了空間。那么為什么要使用對齊呢?體系結構的對齊和不對齊,是在時間和空間上的一個權衡。對齊節省了時間。假設一個體系結構的字長為w,那么它同時就假設了在這種體系結構上對寬度為w的數據的處理最頻繁也是最重要的。它的設計也是從優先提高對w位數據操作的效率來考慮的。有興趣的可以google一下,人家就可以跟你解釋的,一大堆的道理。

最后順便提一點,在設計結構體的時候,一般會尊照一個習慣,就是把占用空間小的類型排在前面,占用空間大的類型排在后面,這樣可以相對節約一些對齊空間。

三.既有結構,為何引入聯合

聯合定義

union WordToType

{

  unsigned char Tbyte[2];

  unsigned short Tword;

}Word;

從定義形式上看,“聯合”和“結構體”非常相似,但二者在本質上卻有很大差距。首先,在結構體中每個成員都有自己的內存,一個結構體變量的總長度等於各個成員長度的總和。而在聯合中,各個成員共享一段內存空間,一個聯合變量的長度等於各個成員中長度最大的那個成員的長度。聯合體對數據的引用與結構體對數據的引用方式相同,都必須通過聯合或結構體中的元素引用,而不能引用聯合體變量。例如,對於上面的聯合,下面的操作是錯誤的

printf("%d",Word);

聯合體的共享不是指把各個成員同時裝入一個聯合體內,而是數據根據聯合體的定義可以展現成不同的形式,同時向用戶傳遞不同信息的過程。

幾個特點:

1.同一個聯合體可用來存放幾種不同類型的成員,但是在每一瞬間只能存放其中的和種,而不是同時存放幾種,換句話說,每一瞬間只有一個成員起作用,其他的成員不起作用。

2.共用體變量中起作用的成員是最后一次存放的成員,在存入一個新成員后,原來的成員就失去作用

3.共用體變量的地址和其各成員的地址都是同一個地址

4.不能地共用體變量名賦值,也不能企圖引用變量名來得到一個值,並且也不能在定義共用體變量時對它進行初始化

5.不能把共用體變量作為函數參數,也不能作為函數的返回值,但可使用指向使用指向共用體變量的指針

6.共用體類型可出現在結構體類型的定義中,也可定義共用體數組;反之,結構體也可出現在共用體類型的定義中,數組也可作為共用體的成員

 


免責聲明!

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



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