1. 程式人生 > >結構體成員的記憶體分佈與對齊

結構體成員的記憶體分佈與對齊

我們先看一道IBM和微軟的筆試題:

IBM筆試題:

struct{

 short   a1;

short   a2;

 short   a3;

 }A;

 struct{

 long   a1;

 short   a2;  

 }B;  

 sizeof( A)=6,   sizeof(B)=8,為什麼?  

 注:sizeof(short)=2,sizeof(long)=4

 

微軟筆試題:

struct example1

 {

       short a ;

       long b;

 };

 

 struct example2

 {

 char c;

 example1 struct1;

 short e;   

 };

 

int main(int argc, char*argv[])

{

       example2 e2;

       int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;

       printf("%d,%d,%d\n",sizeof(example1),sizeof(example2),d);

       return 0;

}

輸出結果?

 

要能清除的分析上面的問題就要搞清楚結構體變數的成員在記憶體裡是如何分佈的、成員先後順序是怎樣的、成員之間是連續的還是分散的、還是其他的什麼形式?其實這些問題既和軟體相關又和硬體相關。所謂軟體相關主要是指和具體的程式語言的編譯器的特性相關,編譯器為了優化CPU訪問記憶體的效率,在生成結構體成員的起始地址時遵循著某種特定的規則,這就是所謂的 結構體成員“對齊”;所謂硬體相關主要是指CPU的“位元組序”問題,也就是大於一個位元組型別的資料如int型別、short型別等,在記憶體中的存放順序,即單個位元組與高低地址的對應關係。位元組序分為兩類:Big-Endian和Little-Endian,有的文章上稱之為“大端”和“小端”,他們是這樣定義的:

Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端;Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。

Intel、VAX和Unisys處理器的計算機中的資料的位元組順序是Little-Endian,IBM 大型機和大多數Unix平臺的計算機中位元組順序是Big –Endian。

關與Big-Endian和Little-Endian問題本文暫不做詳細討論,本文將以小端機(此處為intel x86架構的計算機)、OS:WindowsXp和VC++6.0編譯器來詳細討論結構體成員的“對齊”問題。

前面說了,為了優化CPU訪問記憶體的效率,程式語言的編譯器在做變數的儲存分配時就進行了分配優化處理,優化規則大致原則是這樣:

  對於n位元組的元素(n=2,4,8,...),它的首地址能被n整除,這種原則稱為“對齊”,如WORD(2位元組)的值應該能被2整除的位置,DWORD(4位元組)應該在能被4整除的位置。 

對於結構體來說,結構體的成員在記憶體中順序存放,所佔記憶體地址依次 增高,第一個成員處於低地址處,最後一個成員處於最高地址處,但結構體成員的記憶體分配不一定是連續的,編譯器會對其成員變數依據前面介紹的 “對齊”原則進行處理。對待每個成員類似於對待單個n位元組的元素一樣,依次為每個元素找一個適合的首地址,使得其符合上述的“對齊”原則。通常編譯器中可以設定一個對齊引數n,但這個n並不是結構體成員實際的對齊引數,VC++6.0中結構體的每個成員實際對齊引數N通常是這樣計算得到的N=min(sizeof(該成員型別),n)(n為VC++6.0中可設定的值)。

成員的記憶體分配規律是這樣的:從結構體的首地址開始向後依次為每個成員尋找第一個滿足條件的首地址x,該條件是x % N = 0,並且整個結構的長度必須為各個成員所使用的對齊引數中最大的那個值的最小整數倍,不夠就補空位元組

結構體中所有成員的對齊引數N的最大值稱為結構體的對齊引數

VC++6.0中n預設是8個位元組,可以修改這個設定的對齊引數,方法為在選單“工程”的“設定”中的“C/C++”選項卡的“分類”中 “CodeGeneration ”的“Struct member alignment” 中設定,1byte、2byte、4byte、8byte、16byte等幾種,預設為8byte

也可以程式控制,採用指令:#pragma  pack(xx)控制

如#pragma   pack(1),1位元組對齊,#pragma   pack(4),4位元組對齊

#pragma  pack(16),16位元組對齊

 

接下來我們將分不同的情況來詳細討論結構體成員的分佈情況,順便提醒一下,

常見型別的長度:

Int 4byte,

Short 2byte,

Char 1byte,

Double 8byte,

Long 4byte

 

讓我們先看下例:

struct A

{

       char      c;    //1byte

       double    d;    //8byte

       short       s;     //2byte

       int           i;     //4byte

};

int main(int argc, char*argv[])

{

 

       A strua;

       printf("%len:d\n",sizeof(A));

       printf("%d,%d,%d,%d",&strua.c,&strua.d,&strua.s,&strua.i);

       return 0;

}

1)n設定為8byte時

結果:len:24,

1245032,1245040,1245048,1245052

記憶體中成員分佈如下:

strua.c分配在一個起始於8的整數倍的地址1245032(為什麼是這樣讀者先自己思考,讀完就會明白),接下來要在strua.c之後分配strua.d,由於double為8位元組,取N=min(8,8),8位元組來對齊,所以從strua.c向後找第一個能被8整除的地址,所以取1245032+8得1245040, strua.s 為2byte小於引數n,所以N=min(2,8),即N=2,取2位元組長度對齊,所以要從strua.d後面尋找第一個能被2整除的地址來儲存strua.s,由於strua.d後面的地址為1245048可以被2整除,所以strua.s緊接著分配,現在來分配strua.i,int為4byte,小於指定對齊引數8byte,所以N=min(4,8)取N=4byte對齊,strua.s後面第一個能被4整除地址為1245048+4,所以在1245048+4的位置分配了strua.i,中間補空,同時由於所有成員的N值的最大值為8,所以整個結構長度為8byte的最小整數倍,即取24byte其餘均補0.

於是該結構體的對齊引數就是8byte。

 

2)當對齊引數n設定為16byte時,結果同上,不再分析

 

3)當對齊引數設定為4byte時

上例結果為:Len:20

1245036,1245040,1245048,1245052

記憶體中成員分佈如下:

 

Strua.c起始於一個4的整數倍的地址,接下來要在strua.c之後分配strua.d,由於strua.d長度為8byte,大於對齊引數4byte,所以N=min(8,4)取最小的4位元組,所以向後找第一個能被4整除的地址來作為strua.d首地址,故取1245036+4,接著要在strua.d後分配strua.s,strua.s長度為2byte小於4byte,取N=min(2,4)2byte對齊,由於strua.d後的地址為1245048可以被2

整除,所以直接在strua.d後面分配,strua.i的長度為4byte,所以取N=min(4,4)4byte對齊,所以從strua.s向後找第一個能被4整除的位置即1245048+4來分配和strua.i,同時N的最大值為4byte,所以整個結構的長度為4byte的最小整數倍16byte

 

4)當對齊引數設定為2byte時

上例結果為:Len:16

1245040,1245042,1245050,1245052

Strua.c分配後,向後找一第一個能被2整除的位置來存放strua.d,依次類推

 

5)1byte對齊時:

上例結果為:Len:15

1245040,1245041,1245049,1245051

此時,N=min(sizeof(成員),1),取N=1,由於1可以整除任何整數,所以各個成員依次分配,沒有間空,如下圖所示:

6)當結構體成員為陣列時,並不是將整個陣列當成一個成員來對待,而是將陣列的每個元素當一個成員來分配,其他分配規則不變,如將上例的結構體改為:

struct A

{

       char c;    //1byte

       double    d;    //8byte

       short       s;     //2byte

       char       szBuf[5];      

};

對齊引數設定為8byte,則,執行結果如下:

Len:24

1245032,1245040,1245048,1245050

Strua 的s分配後,接下來分配Strua 的陣列szBuf[5],這裡要單獨分配它的每個元素,由於是char型別,所以N=min(1,8),取N=1,所以陣列szBuf[5]的元素依次分配沒有間隙。

 

7)當結構中有成員不是一個完整的型別單元,如int或short型,而是該型別的一段時,即位段時,

struct A

{

    int     a1:5;

    int     a2:9;

    char   c;

    int     b:4;

    short  s;

 

};

    對於位段成員,儲存是按其型別分配空間的,如int 型就分配4個連續的儲存單元,如果是相鄰的同類型的段位成員就連續存放,共用儲存單元,此處如a1,a2將公用一個4位元組的儲存單元,當該型別的長度不夠用時,就另起一個該型別長度的儲存空間。有位段時的對齊規則是這樣:同類型的、相鄰的可連續在一個型別的儲存空間中存放的位段成員作為一個該型別的成員變數來對待,不是同類型的、相鄰的位段成員,分別當作一個單獨得該型別的成員來對待,分配一個完整的型別空間,其長度為該型別的長度,其他成員的分配規則不變,仍然按照前述的對齊規則進行。

對於 struct A,VC++6.0中n設定為8時,sizeof(A)=16,記憶體分佈:

又如:

struct   B    

  {  

  int   a:5;    

  int   b:7;    

  int   c:6;

  int   d:9;

  char  e:2;

  int   x;

  }; 

Vc++6.0的對齊引數設定為8、16、4位元組對齊時,sizeof(A)=12記憶體分佈為:

(灰色部分未使用)

當對齊引數設定為2位元組時:(灰色部分未使用)sizeof(A)=10

 

又如intel的筆試題:

#include      “stdafx.h”  

  #include   <iostream.h>  

  struct   bit  

  {  

int   a:3;

    int   b:2;

    int   c:3;

  };

  int   main(int   argc,   char*   argv[])    

  {    

bit   s;    

  char   *c   =   (char*)&s;    

  *c   =   0x99;    

 cout<<s.a<<endl<<s.b<<endl<<s.c<<endl;  

  return   0;    

  }

 Output:?  

 執行的結果是   1   -1   -4  

結構bit的成員在記憶體中由低地址到高地址順序存放,執行*c=0x99;後成員的記憶體分佈情況為:

 

8)當結構體成員是結構體型別時,那麼該過程是個遞迴過程,且把該成員作為一個整體來對待,如(微軟筆試題):

struct example1

 {

       short a ;

       long b;

 };

 

 struct example2

 {

 char c;

 example1 struct1;

 short e;   

 };

 

int main(int argc, char*argv[])

{

       example2 e2;

       int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;

       printf("%d,%d,%d\n",sizeof(example1),sizeof(example2),d);

       return 0;

}

8byte對齊時,結果為:

8,16,4

記憶體分佈為:

因為example1的對齊引數為4,分配完c後要接著分配struct1,這時的對齊引數為min(struct1的對齊引數,指定對齊引數),開始分配struct1,在struct1的成員分配過程中又是按照前述的規則來分配的。

 

關於結構體記憶體對齊

記憶體對齊”應該是編譯器的“管轄範圍”。編譯器為程式中的每個“資料單元”安排在適當的位置上。但是C語言的一個特點就是太靈活,太強大,它允許你干預“記憶體對齊”。如果你想了解更加底層的祕密,“記憶體對齊”對你就不應該再透明瞭。

一、記憶體對齊的原因
大部分的參考資料都是如是說的:
1、平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。
2、效能原因:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。

二、對齊規則
每個特定平臺上的編譯器都有自己的預設“對齊係數”(也叫對齊模數)。程式設計師可以通過預編譯命令#pragmapack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。

對齊步驟:
1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragmapack指定的數值和這個資料成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。
3、結合1、2顆推斷:當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。
備註:陣列成員按長度按陣列型別長度計算,如char t[9],在第1步中資料自身長度按1算,累加結構體時長度為9;第2步中,找最大資料長度時,如果結構體T有複雜型別成員A的,該A成員的長度為該複雜型別成員A的最大成員長度。

三、試驗
我們通過一系列例子的詳細說明來證明這個規則吧!
我試驗用的編譯器包括GCC3.4.2和VC6.0的C編譯器,平臺為Windows XP + Sp2。

我們將用典型的struct對齊來說明。首先我們定義一個struct:
#pragma pack(n) /* n = 1, 2, 4, 8, 16 */
struct test_t {
 int a;
 char b;
 short c;
 char d;
};
#pragma pack(n)
首先我們首先確認在試驗平臺上的各個型別的size,經驗證兩個編譯器的輸出均為:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4

我們的試驗過程如下:通過#pragmapack(n)改變“對齊係數”,然後察看sizeof(structtest_t)的值。

1、1位元組對齊(#pragma pack(1))
輸出結果:sizeof(structtest_t) = 8 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(1)
struct test_t {
 int a;  /* 長度4< 1 按1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */
 char b;  /* 長度1= 1 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
 short c; /* 長度2> 1 按1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */
 char d;  /* 長度1= 1 按1對齊;起始offset=7 7%1=0;存放位置區間[7] */
};
#pragma pack()
成員總大小=8

2) 整體對齊
整體對齊係數 =min((max(int,short,char), 1) = 1
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 8 /* 8%1=0 */ [注1]

2、2位元組對齊(#pragma pack(2))
輸出結果:sizeof(structtest_t) = 10 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(2)
struct test_t {
 int a;  /* 長度4> 2 按2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */
 char b;  /* 長度1< 2 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
 short c; /* 長度2= 2 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
 char d;  /* 長度1< 2 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9

2) 整體對齊
整體對齊係數 =min((max(int,short,char), 2) = 2
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 10 /* 10%2=0 */

3、4位元組對齊(#pragma pack(4))
輸出結果:sizeof(structtest_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(4)
struct test_t {
 int a;  /* 長度4= 4 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
 char b;  /* 長度1< 4 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
 short c; /* 長度2< 4 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
 char d;  /* 長度1< 4 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9

2) 整體對齊
整體對齊係數 =min((max(int,short,char), 4) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */

4、8位元組對齊(#pragma pack(8))
輸出結果:sizeof(structtest_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(8)
struct test_t {
 int a;  /* 長度4< 8 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
 char b;  /* 長度1< 8 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
 short c; /* 長度2< 8 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
 char d;  /* 長度1< 8 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9

2) 整體對齊
整體對齊係數 =min((max(int,short,char), 8) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */


5、16位元組對齊(#pragma pack(16))
輸出結果:sizeof(structtest_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員資料對齊
#pragma pack(16)
struct test_t {
 int a;  /* 長度4< 16 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
 char b;  /* 長度1< 16 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
 short c; /* 長度2< 16 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
 char d;  /* 長度1< 16 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9

2) 整體對齊
整體對齊係數 =min((max(int,short,char), 16) = 4
整體大小(size)=$(成員總大小) 按 $(整體對齊係數) 圓整 = 12 /* 12%4=0 */

 

記錄型別的記憶體分配!

Packed Record和Record的不同之處!

type

MyRec=Record

var1:integer;

var2,var3,var4,var5,var6,var7,var8:shortint;

var9:integer;

var10:shortint;

var11:integer;

var12,var13:shortint;

end;

...

ShowMessage(intTostr(SizeOf(MyRec)));

結果顯示為18,而按我想象應為16。請高手講解一下Delphi5.0中變數記憶體空間分配機制,因為我有一個數組MyArray:Array[1..1000000]of MyRec;需要考慮節省記憶體問題,

另外不要說我懶不愛看書,我手頭所有關於Delphi的書都沒有提到這個問題。

回答:

顯示的結果應該為28,而不是18!按道理應該是22。用Packed的結果就是22。

擬定義的陣列比較大,應該用packedrecord!

原因如下:

在Windows中記憶體的分配一次是4個位元組的。而Packed按位元組進行記憶體的申請和分配,這樣速度要慢一些,因為需要額外的時間來進行指標的定位。因此如果不用Packed的話,Delphi將按一次4個位元組的方式申請記憶體,因此如果一個變數沒有4個位元組寬的話也要佔4個位元組!這樣就浪費了。按上面的例子來說:

var1:integer;//integer剛好4個位元組!

var2-var5佔用4個位元組,Var6-Var8佔用4個位元組,浪費了一個位元組。

var9:integer//佔用4個位元組;

var10:佔用4個位元組;浪費3個位元組

var11:佔用4個位元組;

var12,var13佔用4個位元組;浪費2個位元組

所以,如果不用packed的話,那麼一共浪費6個位元組!所以原來22個位元組的記錄需要28個位元組的記憶體空間!

****************

回覆人:eDRIVE(eDRIVE) (2001-3-2 17:45:00) 得0分

這是因為在32位的環境中,所有變數分配的記憶體都進行“邊界對齊”造成的。這樣做可以對速度有優化作用;但是單個定義的變數至少會佔用32位,即4個位元組。所以會有長度誤差,你可以用packed關鍵字取消這種優化。

深入的分析,記憶體空間(不是記憶體地址)在計算機中劃分為無數與匯流排寬度一致的單位,單位之間相接的地方稱為“邊界”;匯流排在對記憶體進行訪問時,每次訪問週期只能讀寫一個單位(32bit),如果一個變數橫跨“邊界”的話,則讀或寫這個變數就得用兩個訪問週期,而“邊界對齊”時,只需一個訪問週期,速度當然會有所優化。

Record的資料各個位元組都是對齊的,資料格式比較完整,所以這種格式相對packed佔用的記憶體比較大,

但是因為格式比較整齊,所以電腦讀取這個型別的資料的時候速度比較快。

而PackedRecord對資料進行了壓縮,節省了記憶體空間,當然他的速度也變的慢了。

   type  

       //   Declare    an    unpacked   record  

       TDefaultRecord    =   Record  

           name1        :   string[4];  

           floater    :   single;  

           name2        :   char;  

           int            :   Integer;  

       end;  

       //   Declare    a    packed   record  

       TPackedRecord    =   Packed    Record  

           name1        :   string[4];  

           floater    :   single;  

           name2        :   char;  

           int            :   Integer;  

       end;  

   var  

       defaultRec    :   TDefaultRecord;  

       packedRec      :   TPackedRecord;  

   begin  

       ShowMessage('Default    record   size    =    '+IntToStr(SizeOf(defaultRec)));  

       ShowMessage('Packed    record   size    =    '+IntToStr(SizeOf(packedRec)));  

   end;  

     Default   record    size    =   20  

     Packed   record    size    =   14  

不過,對於現在的作業系統來,packedRecord 節省的那些空間已不用考慮他了。除了做DLL(不用packed容易造成記憶體混亂)和做硬體程式設計時(比如串列埠)程式設計時必須用到packedRecord,其它情況都可以用Record

C的結構體與Delphi中的記錄型別

 

Object Pascal的指標
    一、型別指標的定義。對於指向特定型別的指標,在C中是這樣定義的:
        int *ptr;
        char *ptr;
        與之等價的Object Pascal是如何定義的呢? 
        var
        ptr : ^Integer;
        ptr : ^char; 
        其實也就是符號的差別而已。

    二、無型別指標的定義。C中有void *型別,也就是可以指向任何型別資料的指標。Object Pascal為其定義了一個專門的型別:Pointer。於是,
        ptr : Pointer;
        就與C中的
        void *ptr;
        等價了。

    三、指標的解除引用。要解除指標引用(即取出指標所指區域的值),C 的語法是 (*ptr),Object Pascal則是 ptr^。

    四、取地址(指標賦值)。取某物件的地址並將其賦值給指標變數,C 的語法是
        ptr = &Object;
        Object Pascal 則是
        ptr := @Object;
        也只是符號的差別而已。

    五、指標運算。在C中,可以對指標進行移動的運算,如:
        char a[20];  
        char *ptr=a;  
        ptr++;
        ptr+=2;
        當執行ptr++;時,編譯器會產生讓ptr前進sizeof(char)步長的程式碼,之後,ptr將指向a[1]。ptr+=2;這句使得ptr前進兩個sizeof(char)大小的步長。同樣,我們來看一下Object Pascal中如何實現:
        var
            a : array [1..20] of Char;
            ptr : PChar; //PChar 可以看作 ^Char
        begin
            ptr := @a;
            Inc(ptr); // 這句等價於 C 的 ptr++;
            Inc(ptr, 2); //這句等價於 C 的 ptr+=2;
        end;

    六、動態記憶體分配。C中,使用malloc()庫函式分配記憶體,free()函式釋放記憶體。如這樣的程式碼:
        int *ptr, *ptr2;
        int i;
        ptr = (int*) malloc(sizeof(int) * 20);
        ptr2 = ptr;
        for (i=0; i<20; i++){
            *ptr = i;
            ptr++;
        }
        free(ptr2);
        Object Pascal中,動態分配記憶體的函式是GetMem(),與之對應的釋放函式為FreeMem()(傳統Pascal中獲取記憶體的函式是New()和 Dispose(),但New()只能獲得物件的單個實體的記憶體大小,無法取得連續的存放多個物件的記憶體塊)。因此,與上面那段C的程式碼等價的Object Pascal的程式碼為:
        var ptr, ptr2 : ^integer;
            i : integer;
        begin
            GetMem(ptr, sizeof(integer) * 20); 
                //這句等價於C的 ptr = (int*) malloc(sizeof(int) * 20);
            ptr2 := ptr; //保留原始指標位置
            for i := 0 to 19 do
            begin
                ptr^ := i;
                Inc(ptr);
            end;
            FreeMem(ptr2);
        end;
        對於以上這個例子(無論是C版本的,還是Object Pascal版本的),都要注意一個問題,就是分配記憶體的單位是位元組(BYTE),因此在使用GetMem時,其第二個引數如果想當然的寫成 20,那麼就會出問題了(記憶體訪問越界)。因為GetMem(ptr, 20);實際只分配了20個位元組的記憶體空間,而一個整形的大小是四個位元組,那麼訪問第五個之後的所有元素都是非法的了(對於malloc()的引數同樣)。

    七、字元陣列的運算。C語言中,是沒有字串型別的,因此,字串都是用字元陣列來實現,於是也有一套str打頭的庫函式以進行字元陣列的運算,如以下程式碼:
        char str[15];
        char *pstr;
        strcpy(str, "teststr");
        strcat(str, "_testok");
        pstr = (char*) malloc(sizeof(char) * 15);
        strcpy(pstr, str);
        printf(pstr);
        free(pstr);
        而在Object Pascal中,有了String型別,因此可以很方便的對字串進行各種運算。但是,有時我們的Pascal程式碼需要與C的程式碼互動(比如:用Object Pascal的程式碼呼叫C寫的DLL或者用Object Pascal寫的DLL準備允許用C寫客戶端的程式碼)的話,就不能使用String型別了,而必須使用兩種語言通用的字元陣列。其實,Object Pascal提供了完全相似C的一整套字元陣列的運算函式,以上那段程式碼的Object Pascal版本是這樣的:
        var str : array [1..15] of char;
            pstr : PChar; //Pchar 也就是 ^Char
        begin
            StrCopy(@str, 'teststr'); //在C中,陣列的名稱可以直接作為陣列首地址指標來用
                                      //但Pascal不是這樣的,因此 str前要加上取地址的運算子
            StrCat(@str, '_testok');
            GetMem(pstr, sizeof(char) * 15);
            StrCopy(pstr, @str);
            Write(pstr);
            FreeMem(pstr);
        end;

    八、函式指標。在動態呼叫DLL中的函式時,就會用到函式指標。假設用C寫的一段程式碼如下:
        typedef int (*PVFN)(int); //定義函式指標型別
        int main()
        {
            HMODULE hModule = LoadLibrary("test.dll");
     PVFN pvfn = NULL;
            pvfn = (PVFN) GetProcAddress(hModule, "Function1");
            pvfn(2);
            FreeLibrary(hModule);
        }
        就我個人感覺來說,C語言中定義函式指標型別的typedef程式碼的語法有些晦澀,而同樣的程式碼在Object Pascal中卻非常易懂:
        type PVFN = Function (para : Integer) : Integer;
        var
            fn : PVFN; 
                //也可以直接在此處定義,如:fn : function (para:Integer):Integer;
            hm : HMODULE;
        begin
            hm := LoadLibrary('test.dll');
            fn := GetProcAddress(hm, 'Function1');
            fn(2);
            FreeLibrary(hm);
        end;

以上是一位Delphi高手給我回的貼!