关闭

结构体成员的内存分布与对齐

标签: pascalbytestruct编译器integerobject
9386人阅读 评论(0) 收藏 举报


我们先看一道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高手给我回的贴!

 

2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:392087次
    • 积分:4557
    • 等级:
    • 排名:第7438名
    • 原创:13篇
    • 转载:194篇
    • 译文:0篇
    • 评论:57条
    文章分类
    最新评论