程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 淺析內存對齊與ANSI C中struct型數據的內存結構

淺析內存對齊與ANSI C中struct型數據的內存結構

編輯:關於C++

淺析內存對齊與ANSI C中struct型數據的內存結構。本站提示廣大學習愛好者:(淺析內存對齊與ANSI C中struct型數據的內存結構)文章只能為提供參考,不一定能成為您想要的結果。以下是淺析內存對齊與ANSI C中struct型數據的內存結構正文


這些成績也許對很多同伙來講還有點隱約,那末本文就試著探討它們面前的機密。

起首,至多有一點可以確定,那就是ANSI C包管構造體中各字段在內存中湧現的地位是隨它們的聲明次序順次遞增的,而且第一個字段的首地址等於全部構造體實例的首地址。好比有如許一個構造體:

  struct vector{int x,y,z;} s;
  int *p,*q,*r;
  struct vector *ps;
  p = &s.x;
  q = &s.y;
  r = &s.z;
  ps = &s;

  assert(p < q);
  assert(p < r);
  assert(q < r);
  assert((int*)ps == p);
  // 上述斷言必定不會掉敗

這時候,有同伙能夠會問:"尺度能否劃定相鄰字段在內存中也相鄰?"。 唔,對不起,ANSI C沒有做出包管,你的法式在任什麼時候候都不該該依附這個假定。那這能否意味著我們永久沒法勾畫出一幅更清楚更准確的構造體內存結構圖?哦,固然不是。不外先讓我們從這個成績中臨時抽身,存眷一下另外一個主要成績————內存對齊。

很多現實的盤算機體系對根本類型數據在內存中寄存的地位無限制,它們會請求這些數據的首地址的值是某個數k(平日它為4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱為該數據類型的對齊模數(alignment modulus)。當一品種型S的對齊模數與另外一品種型T的對齊模數的比值是年夜於1的整數,我們就稱類型S的對齊請求比T強(嚴厲),而稱T比S弱(寬松)。這類強迫的請求一來簡化了處置器與內存之間傳輸體系的設計,二來可以晉升讀取數據的速度。好比這麼一種處置器,它每次讀寫內存的時刻都從某個8倍數的地址開端,一次讀出或寫入8個字節的數據,假設軟件能包管double類型的數據都從8倍數地址開端,那末讀或寫一個double類型數據就只須要一次內存操作。不然,我們便可能須要兩次內存操作能力完成這個舉措,由於數據也許正好橫跨在兩個相符對齊請求的8字節內存塊上。某些處置器在數據不知足對齊請求的情形下能夠會失足,然則Intel的IA32架構的處置器則不論數據能否對齊都能准確任務。不外Intel勸告年夜家,假如想晉升機能,那末一切的法式數據都應當盡量地對齊。Win32平台下的微軟C編譯器(cl.exe for 80x86)在默許情形下采取以下的對齊規矩: 任何根本數據類型T的對齊模數就是T的年夜小,即sizeof(T)。好比關於double類型(8字節),就請求該類型數據的地址老是8的倍數,而char類型數據(1字節)則可以從任何一個地址開端。Linux下的GCC奉行的是別的一套規矩(在材料中查得,並未驗證,如毛病請斧正):任何2字節年夜小(包含單字節嗎?)的數據類型(好比short)的對齊模數是2,而其它一切跨越2字節的數據類型(好比long,double)都以4為對齊模數。

如今回到我們關懷的struct下去。ANSI C劃定一種構造類型的年夜小是它一切字段的年夜小和字段之間或字段尾部的填充區年夜小之和。嗯?填充區?對,這就是為了使構造體字段知足內存對齊請求而額定分派給構造體的空間。那末構造體自己有甚麼對齊請求嗎?有的,ANSI C尺度劃定構造體類型的對齊請求不克不及比它一切字段中請求最嚴厲的誰人寬松,可以更嚴厲(但此非強迫請求,VC7.1就僅僅是讓它們一樣嚴厲)。我們來看一個例子(以下一切實驗的情況是Intel Celeron 2.4G + WIN2000 PRO + vc7.1,內存對齊編譯選項是"默許",即不指定/Zp與/pack選項):

  typedef struct ms1
  {
     char a;
     int b;
  } MS1;

假定MS1按以下方法內存結構(本文一切表示圖中的內存地址從左至右遞增):
       _____________________________

       |   a   |        b          |

       +---------------------------+
Bytes:    1             4
由於MS1中有最強對齊請求的是b字段(int),所以依據編譯器的對齊規矩和ANSI C尺度,MS1對象的首地址必定是4(int類型的對齊模數)的倍數。那末上述內存結構中的b字段能知足int類型的對齊請求嗎?嗯,固然不克不及。假如你是編譯器,你會若何奇妙支配來知足CPU的嗜好呢?呵呵,經由1毫秒的艱難思慮,你必定得出了以下的計劃:
       _______________________________________
       |       |///////////|                 |
       |   a   |//padding//|       b         |
       |       |///////////|                 |
       +-------------------------------------+
Bytes:    1         3             4
這個計劃在a與b之間多分派了3個填充(padding)字節,如許當全部struct對象首地址知足4字節的對齊請求時,b字段也必定能知足int型的4字節對齊劃定。那末sizeof(MS1)明顯就應當是8,而b字段絕對於構造體首地址的偏移就是4。異常好懂得,對嗎?如今我們把MS1中的字段交流一下次序:

  typedef struct ms2
  {
     int a;
     char b;
  } MS2;

也許你以為MS2比MS1的情形要簡略,它的結構應當就是

       _______________________

       |     a       |   b   |

       +---------------------+
Bytes:      4           1
由於MS2對象異樣要知足4字節對齊劃定,而此時a的地址與構造體的首地址相等,所以它必定也是4字節對齊。嗯,剖析得有事理,可是卻不周全。讓我們來斟酌一下界說一個MS2類型的數組會湧現甚麼成績。C尺度包管,任何類型(包含自界說構造類型)的數組所占空間的年夜小必定等於一個零丁的該類型數據的年夜小乘以數組元素的個數。換句話說,數組各元素之間不會有閒暇。依照下面的計劃,一個MS2數組array的結構就是:

|<-    array[1]     ->|<-    array[2]     ->|<- array[3] .....
__________________________________________________________

|     a       |   b   |      a       |   b  |.............

+----------------------------------------------------------
Bytes:  4         1          4           1

當數組首地址是4字節對齊時,array[1].a也是4字節對齊,可是array[2].a呢?array[3].a ....呢?可見這類計劃在界說構造體數組時沒法讓數組中一切元素的字段都知足對齊劃定,必需修正成以下情勢:
       ___________________________________
       |             |       |///////////|
       |     a       |   b   |//padding//|
       |             |       |///////////|
       +---------------------------------+
Bytes:      4           1         3

如今不管是界說一個零丁的MS2變量照樣MS2數組,均能包管一切元素的一切字段都知足對齊劃定。那末sizeof(MS2)依然是8,而a的偏移為0,b的偏移是4。

好的,如今你曾經控制了卻構體內存結構的根本原則,測驗考試剖析一個略微龐雜點的類型吧。

  typedef struct ms3
  {
     char a;
     short b;
     double c;
  } MS3;

我想你必定能得出以下准確的結構圖:

        padding 

      _____v_________________________________
      |   |/|     |/////////|               |
      | a |/|  b  |/padding/|       c       |
      |   |/|     |/////////|               |
      +-------------------------------------+
Bytes:  1  1   2       4            8

sizeof(short)等於2,b字段應從偶數地址開端,所以a的前面填充一個字節,而sizeof(double)等於8,c字段要從8倍數地址開端,後面的a、b字段加上填充字節曾經有4 bytes,所以b前面再填充4個字節便可以包管c字段的對齊請求了。sizeof(MS3)等於16,b的偏移是2,c的偏移是8。接著看看構造體中字段照樣構造類型的情形:

  typedef struct ms4
  {
     char a;
     MS3 b;
  } MS4;

MS3中內存請求最嚴厲的字段是c,那末MS3類型數據的對齊模數就與double的分歧(為8),a字段前面應填充7個字節,是以MS4的結構應當是:
       _______________________________________
       |       |///////////|                 |
       |   a   |//padding//|       b         |
       |       |///////////|                 |
       +-------------------------------------+
 Bytes:    1         7             16

明顯,sizeof(MS4)等於24,b的偏移等於8。

在現實開辟中,我們可以經由過程指定/Zp編譯選項來更改編譯器的對齊規矩。好比指定/Zpn(VC7.1中n可所以1、2、4、8、16)就是告知編譯器最年夜對齊模數是n。在這類情形下,一切小於等於n字節的根本數據類型的對齊規矩與默許的一樣,然則年夜於n個字節的數據類型的對齊模數被限制為n。現實上,VC7.1的默許對齊選項就相當於/Zp8。細心看看MSDN對這個選項的描寫,會發明它慎重申饬了法式員不要在MIPS和Alpha平台上用/Zp1和/Zp2選項,也不要在16位平台上指定/Zp4和/Zp8(想一想為何?)。轉變編譯器的對齊選項,對比法式運轉成果從新剖析下面4種構造體的內存結構將是一個很好的溫習。

到了這裡,我們可以答復本文提出的最初一個成績了。構造體的內存結構依附於CPU、操作體系、編譯器及編譯時的對齊選項,而你的法式能夠須要運轉在多種平台上,你的源代碼能夠要被分歧的人用分歧的編譯器編譯(試想你為他人供給一個開放源碼的庫),那末除非相對必須,不然你的法式永久也不要依附這些詭異的內存結構。趁便說一下,假如一個法式中的兩個模塊是用分歧的對齊選項分離編譯的,那末它極可能會發生一些異常奧妙的毛病。假如你的法式確切有很難懂得的行動,不防細心檢討一下各個模塊的編譯選項。

思慮題:請剖析上面幾種構造體在你的平台上的內存結構,並試著尋覓一種公道支配字段聲明次序的辦法以盡可能節儉內存空間。

    A. struct P1 { int a; char b; int c; char d; };
    B. struct P2 { int a; char b; char c; int d; };
    C. struct P3 { short a[3]; char b[3]; };
    D. struct P4 { short a[3]; char *b[3]; };
    E. struct P5 { struct P2 *a; char b; struct P1 a[2];  };

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved