內存對齊與自定義類型

一、內存對齊


  (一)、爲什麼會有內存對齊?

    1、爲了提高程序的性能,數據結構(尤其是棧)應該儘可能的在自然邊界上對齊。原因是爲了訪問未對齊的內存,處理器需要進行兩次訪問,而訪問對齊的內存,只需要一次就夠了。這種方式稱作“以空間換時間”在很多對時間複雜度有要求問題中,會採用這種方法。


  Center

  2、內存對齊能夠增加程序的可移植性,因爲不是所有的平臺都能隨意的訪問內存,有些平臺只能在特定的地址處處讀取內存。


   一般情況下內存對齊是編譯器的事情,我們不需要考慮,但有些問題還是需要考慮的,畢竟c/c++是直接操作內存的語言,需要了解程序在內存中的分佈和運行原理。



(二)、內存對齊:那麼如何對齊呢?

    對齊原則:數據存放的起始位置是自身大小的整數倍處。

    例:內存從0地址處開始。

       char1個字節,所以對齊後它可以存放到地址是1的倍數處。

       short2個字節,所以對齊後它可以存放到地址是2的倍數處。

       int是4個字節,所以對齊後它可以存放到地址是4的倍數處。

       double是8個字節,所以對齊後它可以存放到地址是8的倍數處。


現在相信你已經明白了內存對齊的原則,接下來我們看看結構體內存對齊。


(三)、在瞭解結構體內存對齊之前先來了解幾個概念:

    1、默認對齊數:在vs下內存對齊數默認是8,linux是4.可以通過#program pack  ()來修改默認對齊數。

    2、偏移:相對於起始位置的的位置。例如起始位置是2,那麼2就是0偏移處,3就是1偏移處。

Center


   3、對齊數:變量自身的大小和默認對齊數之中的最小值。假設默認對齊數是8,int類型的對齊數就是4.因爲int大小是4,小於8



二、結構體內存對齊原則:

  1、結構或聯合的數據成員,第一個成員放到0偏移的地方,以後每個數據成員都放到自身對齊數的整數倍偏移處。

  2、結構體的大小必須是最大對齊數的整數倍。

   例1:

       struct stu

       {

         char   c;          //對齊數是1

         short  b;          //對齊數是2

         double d;           //對齊數是8

         int     i;         //對齊數是4

       };


Center


   例2:

       struct stu

       {

          char   c;          //對齊數是1

          short  b;          //對齊數是2

          struct A

          {

             double d;        //對齊數是8

          };

          int     i;         //對齊數是4

       }s;

    嵌套結構體的大小,其分析方法還是一樣,最大對齊數是8,sizeof(s)=24。



三、自定義類型 


(一)、結構體聲明

   1、沒有標籤,不完整的聲明。同時還定義一個變量。

                    struct

                   {

                          charc;

                           shortb;

                           inti;

                    }t1;


   2、有標籤的聲明,但沒定義變量的聲明。

                   struct    A

                  {

                      charc;

                      shortb;

                      inti;

                  };

     //定義一個變量struct A *s1; 

     //注意,在同一個程序中,同時聲明1、2兩個結構體,則1、2兩個結構體會被認爲是不同類型的。所以 s1=&t1是錯誤的。



   3、有標籤的聲明,同時還定義一個變量。

                    struct    A

                   {

                        charc;

                        shortb;

                        inti;

                    }t3;


   4、聲明的同時對結構體重命名爲A.

                     typedef  struct    A

                    {

                         charc;

                         shortb;

                         inti;

                     }A;


   5、先有雞還是先有蛋

                       struct B                                     //無論哪個放到前面都不對

                      {

                           structAa;

                       }s;

                       structA

                        {

                           structBb;

                         };


   如果兩個結構體相互嵌套,則在聲明的時候需要對其中一個結構體進行不完整的聲明。

                          structA;

                          struct B

                             {

                                   structAa;

                              }s;

                         structA

                             {

                                    structBb;

                             };



(二)、結構體的初始化:

     例如:

                   typedef  struct    A

                  {

                       charc;

                       shortb;

                       inti;

                  }A;

                 As1 = {'c', 2 ,  4  };



(三)、結構體的自引用:(結構體的自引用通常會用在鏈表這種線性結構中用到)

   

  1、錯誤的自引用方式,很容易理解的,結構體裏面又有結構體,這樣一直循環下去。(從前有座廟,廟裏有個老和尚,老和尚給小和尚講故事..........^v^)

                     typedef struct   A

                      {

                             intdata;

                             structAn;                                         //死循環

                       }A;


  2、錯誤的只引用,因爲結構體被重新命名爲A是在引用之後。

                     typedef struct                                         //在結構體自引用的時候標籤不能省略。

                     {

                               intdata;

                           An;                                                         //必須使用完整的結構體名稱

                     }A;


  3、正確的方式

                  typedef struct   A

                 {

                         intdata;

                         structA *n;            //用完整的結構體名稱,聲明一個結構體指針,

                  }A;



(四)、結構體做參數傳遞的效率:

   當結構體很大時,結構體在作爲參數傳遞時,我們傳遞它的地址,這樣能夠提高效率,如果你不想改變結構體內容,則在形參處加上const就行。


(五)、柔性數組:

   在結構體中最後一個成員允許是未知大小的數組,這個數組成爲柔性數組(柔性數組之前至少有一個成員變量)

             typedef  struct    A

               {

                     inti;

                      char  a[];

               }A;

   含有柔性數組的結構體大:這樣的結構體,它的大小不包括柔性數組,所以sizeof(A)=4;空結構體的大小是1;



(六)、位段(位域):


  1、概念:在一個結構體中以位爲單位來指定成員所佔內存的實際大小,這種以位爲單位的成員我們稱爲位段,位段是一種特殊的結構體,位段的聲明和任何普通的結構體成員聲明類似,如下:


         Struct 位段結構體名

             {

                   Unsigned 位段名:位段長度;

                   Unsigned 位段名:位段長度;

………………..

                   Unsigned 位段名:位段長度;


             }位段結構體變量名;


   但有兩個例外,首先位段成員必須聲明成int ,unsigned int, signed int,。其次,在成員的後面是一個冒號和一個整數,這個整數指定該位段所佔用位的個數。(實際驗證後發現char類型也可以,但是注意,位段中不能將int 和char 混合使用)。


  2、 位段使用時需要注意是:

        1、位段結構體中的成員不能使用數組和指針,但結構體變量可以使數組或者指針。

        2、因爲數組和指針都是以字節爲單位的,同理也不能用&獲取位段的地址。

        3、位段不支持移植。

  例1:聲明一個位段,我們先來分析一下他在計算機裏面是如何存儲的(一個無符號的int是4字節)。        

               struct tagAAA

                {

                  unsigned int a : 1;

                  unsigned int b : 2;

                  unsigned int c : 6;

                  unsigned int d : 4;

                  unsigned int e;

                 }AAA_S;

Center


   由此我們可以明白位段的優點,本來定義了5個成員,需要5個存儲單位,但是使用位段後只需要4個存儲空間就足夠了。


  3、優點:

    但它的成員是一個或多個位的字段,這些不同長度的字段實際上是存儲於一個或多個整形變量中,他的優點是能夠以較少的內存單元存儲數據。位段可以用整形形式輸出。



例2:

     struct tagAAA

                {

                  unsigned int a : 1;

                  unsigned int  : 2;           //沒有聲明變量,但是卻指定位段大小,稱爲佔位。

                  unsigned int c : 6;

                  unsigned int d : 4;

                  unsigned int e;             //沒有指定位段大小,默認爲自身類型的大小

                 }AAA_S;                


             




(七)、聯合


  1、聯合的聲明:

          typedefunionA

           {

                inti;

                charc;

            }A;


  2、聯合的特點:

    聯合成員之間共用同一塊空間。聯合的大小等於成員中所佔內存最大變量大小。可以用來測大小端。



(八)、枚舉:

    1、聲明:

           typedefenumA

           {

                   zero,

                   one,

                    two

            }A;

如果沒有對枚舉成員進行初始化時,則默認枚舉成員從0開始依次遞增


 注意:

      1、在同一個程序中,不能不能聲明同名的枚舉類型

      2、在同一個程序中,不同的枚舉類型的枚舉成員不能同名。

      3、任何枚舉的大小都是4


  2、枚舉與#define 標識符之間區別:

      1、#define 標識符在預編譯期間進行簡單替換。枚舉類型在編譯的時候確定其值。

      2、枚舉常量可以調試,#define 標識符不可以。

      3、枚舉一次可以定義大量的枚舉量。


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