結構體成員的內存分佈與對齊

我們先看一道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高手給我回的貼!

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