分享

Delphi指针类型浅析(附件)

 quasiceo 2014-10-01
2010-04-26 18:12 448人阅读 评论(0) 收藏 举报

目录(?)[+]

附件资料
*指针的使用(代码)

示例:简单的指针应用

代码:

procedure TForm1.Button2Click(Sender: TObject);

var

    a: Integer;

    p: ^Integer;

begin

    with self.Memo1.Lines do

    begin

        a := 100;

        Add('数据内容:' + inttostr(a));

        //---

        p := @a;

        Add('数据地址:' + inttostr(Integer(p)));

        //---

        Add('数据地址指向的内容:' + inttostr(p^));

    end;

end;

 

procedure TForm1.Button2Click(Sender: TObject);

type

    PInteger = ^Integer;

    PPInteger = ^PInteger;

var

    a: Integer;

    p: PInteger;

    pp: PPInteger;

begin

    with self.Memo1.Lines do

    begin

        a := 100;

        Add('Integer数据内容:' + inttostr(a));

        //---

        p := @a;

        Add('Integer指针内容:' + inttostr(Integer(p)));

        Add('Integer指针指向的数据:' + inttostr(p^));

        //---

        pp := @p;

        Add('PInteger指针内容:' + inttostr(Integer(pp)));

        Add('PInteger指针指向的数据:' + inttostr(Integer(pp^)));

    end;

end;

 

*指针的使用(汇编)

示例:简单的指针应用

说明:

对于编译器来说,指针的类型可以用来标明地址所指向区域的大小、所指向的类型(整型、对象、方法),以及进行指针运算时指针偏移的长度。对于有类型指针的操作最终反映到编译器的可执行代码中,如果不参考可执行代码,单凭一个内存地址,我们并无法判断地址所指向的数据类型。

代码:

procedure TForm1.Button2Click(Sender: TObject);

var

    a:array[0..1] of Integer;

    b: Integer;

    p: ^Integer;

begin

    a[0] := 1;

    a[1] := 2;

    //---

    p := @a[0];

    b := p^;

    //---

    Inc(p);

    b := p^;

end;

汇编:

编译器根据源代码中声明的变量类型分配内存如下:

[ebp - $04]存储为self,大小为4字节,因为self声明为对象指针

[ebp - $08]标识为a[1],大小为4字节,因为a[0]声明为Integer

[ebp - $0c]标识为a[0]

[ebp - $10]标识为b,大小为4字节,因为b声明为Integer

[ebp - $14]标识为p,大小为4字节,因为p声明为Integer指针

[ebp - $18]存储为Sender,大小为4字节,因为Sender声明为对象指针

其中“ebp - $04为内存地址,“[ebp - $04]”为地址所指向的数据,ebpeax32位寄存器可以用于存取一个4字节数据。

 

 

 

代码:

procedure TForm1.Button2Click(Sender: TObject);

var

    a:array[0..1] of Byte;

    b: Byte;

    p: ^Byte;

begin

    a[0] := 1;

    a[1] := 2;

    //---

    p := @a[0];

    b := p^;

    //---

    Inc(p);

    b := p^;

end;

汇编:

编译器根据源代码中声明的变量类型分配内存如下:

[ebp - $04]存储为self,大小为4字节,因为self声明为对象指针

[ebp - $05]标识为a[1],大小为4字节,因为a[0]声明为Integer

[ebp - $06]标识为a[0]

[ebp - $07]标识为b,大小为4字节,因为b声明为Integer

[ebp - $0c]标识为p,大小为4字节,因为p声明为Integer指针

[ebp - $10]存储为Sender,大小为4字节,因为Sender声明为对象指针

其中编译器为了实现数据对齐,所以申请了大小为$10的空间。

 

 

*复杂数据类型指针(代码)

示例:字符串指针

代码:

procedure TForm1.Button2Click(Sender: TObject);

type

    buffer = string[255];

    ptr = ^buffer;

var

    b1: buffer;

    b2: ptr;

begin

    with self.Memo1.Lines do

    begin

        b1 := 'zhang';

        Add('数据内存大小:' + IntToStr(SizeOf(b1)));

        Add('数据内容大小:' + IntToStr(PByte(@b1)^));

        //---

        b2 := @b1;

        Add('指针大小:' + IntToStr(SizeOf(b2)));

        Add('指针指向数据内存大小:' + IntToStr(SizeOf(b2^)));

    end;

end;

说明:

首先,ptr是一个指针类型,而b2是这个指针类型的变量。

其次,ptr是一个指向255长度的短字符串类型的指针,加上一个看不见的计数字节,共256B。当ptr没有分配内存空间,也没初始化时指向的地址是随机的,随意引用可能会出问题。但是只要它指向了某个地址,就代表它指向了256B的空间,它可以指向任何有效的字符串变量,但是无论被指向的字符串有多长,b2^的长度始终是256

 

*记录指针(代码)

示例:记录指针的应用

代码:

procedure TForm1.Button2Click(Sender: TObject);

type

    PTMyRecord = ^TMyRecord;

    TMyRecord = record

        Data1:Integer;

        Data2:Integer;

    end;

var

    ARecord: TMyRecord;

    pRecord: PTMyRecord;

begin

    pRecord := @ARecord;

    ARecord.Data1 := 11;

    pRecord^.Data2 := 22;

    pRecord.Data2 := 33;

end;

 

*过程指针(代码)

示例:方法指针的结构

说明:

1)、过程/函数指针是一个32位的指针

代码:

procedure TForm1.Button2Click(Sender: TObject);

type

    TMyMethod = function(X: Integer): Integer;

var

    F: TMyMethod;

begin

    Self.Memo1.Lines.Add('函数指针大小:' + IntToStr(SizeOf(TMyMethod)));

end;

 

示例:过程指针的应用

说明:

1)、通过赋值语句“过程变量 := 过程”,使得过程变量指向过程或者函数入口地址。

2)、在赋值语句“过程变量 := 过程”中,左边变量的类型决定了右边的过程或者方法指针解释。

3)、可以使用过程变量引用声明的过程或者函数。

4)、无论何时一个过程变量(procedural variable)出现在一个表达式中,它表示调用所指向的函数或者过程。

5)、对于过程变量P @PP转换成一个包含地址的无类型的指针变量(即@P等价于无类型指针P),此时可以把一个无类型的指针值赋给过程变量P

代码:

function SomeFunction(X: Integer): Integer;

begin

    ShowMessage(IntToStr(X));

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function(X: Integer): Integer;

    I: Integer;

begin

    F := SomeFunction; //--f赋值

    I := F(4);         //--调用所指向的函数

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function(X: Integer): Integer;

    I: Integer;

begin

    F := SomeFunction; //--f赋值

    I := F(4); //--调用所指向的函数

    //---

    Self.Memo1.Lines.Add('函数指针大小' + IntToStr(SizeOf(@SomeFunction)));

end;

 

示例:过程指针的应用

代码:

function SomeFunction: Integer;

begin

    Result := 0;

    ShowMessage('0');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function: Integer;

    P:Pointer;

begin

    P := @SomeFunction; //--P赋值

    @F := P; //--f赋值

    F;

end;

说明:

可以使用@操作符,把一个无类型的指针值赋给一个过程变量,如调用“@过程变量 := 无类型指针值”,此时过程变量指向这个值。

 

示例:过程指针的应用

代码:

function SomeFunction: Integer;

begin

    Result := 0;

    ShowMessage('0');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F,G: function: Integer;

    I: Integer;

begin

    F := SomeFunction; //--F赋值

    G := F; //--F的值拷贝给G

    I := G; //--调用函数

end;

说明:

第一句获得函数的入口,第二句将指针复制,第三句获得函数的返回值。 

 

示例:过程指针的函数调用

代码:

function SomeFunction: Integer;

begin

    Result := 0;

end;

 

function MyFunction: Integer;

begin

    Result := 0;

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function: Integer;

    I: Integer;

begin

    F := SomeFunction; //--f赋值

    if F = MyFunction then

        ShowMessage('比较的函数返回值得结果');

end;

说明:

   if语句中,F的出现导致一个函数调用;编译器调用F指向的函数,然后调用Myfunction,比较结果。这个规则是无论何时一个过程变量出现在一个表达式中,它表示调用所指向的函数或者过程。有时F指向一个过程(没有返回值),或者f指向一个需要参数的函数,则前面的语句会产生一个编译错误。

 

示例:比较过程入口地址

代码:

function SomeFunction: Integer;

begin

    Result := 0;

end;

 

function MyFunction: Integer;

begin

    Result := 0;

    ShowMessage('123');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function: Integer;

begin

    F := MyFunction; //--f赋值

    if @F = @MyFunction then

        ShowMessage('比较函数地址');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    F: function: Integer;

    P:Pointer;

begin

    F := MyFunction; //--f赋值

    P := @F;

    if P = @MyFunction then

        ShowMessage('比较函数地址');

end;

说明:

@FF转换成一个包含地址的无类型的指针变量,@myfunction返回myfunction的地址。

 

*方法指针(代码)

示例:方法指针的结构

说明:

1)、方法指针指向一个结构,其结构如下

 TMethod = record

  Code: Pointer;//指向过程/函数的指针

     Data: Pointer;//指向对象的指针

 end;

代码:

type

    TMyObject = class(TObject)

    public

        procedure Method1(Sender: TObject);

    end;

 

procedure TMyObject.Method1(Sender: TObject);

begin

    ShowMessage('123');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

type

    TMyMethod = procedure(Sender: TObject) of object;

var

    AMyMethod: TMyMethod;

    AMyObject: TMyObject;

begin

    with self.Memo1.Lines do

    begin

        Add('方法指针大小:' + IntToStr(SizeOf(TMyMethod)));

        //---

        AMyObject := TMyObject.Create;

        try

            Add('对象指针:' + IntToHex(Integer(AMyObject),2));

            Add('对象方法指针:' + IntToHex(Integer(@TMyObject.Method1),2));

            //---

            AMyMethod := AMyObject.Method1;

            //---

            with TMethod(AMyMethod) do

            begin

                Add('对象指针:' + IntToHex(Integer(Data),2));

                Add('对象方法指针:' + IntToHex(Integer(Code),2));

            end;

            //---

            AMyMethod(AMyObject);

        finally

            AMyObject.Free;

        end;

    end;

end;

 

示例:方法指针的结构

代码:

type

    TMyObject = class(TObject)

    public

        procedure TestMethod(Value: Integer); virtual;

    end;

    TMyObject1 = class(TMyObject)

    public

        procedure TestMethod(Value: Integer); override;

    end;

 

procedure TMyObject.TestMethod(Value: Integer);

begin

    ShowMessage('TMyObject:' + inttostr(Value));

end;

 

procedure TMyObject1.TestMethod(Value: Integer);

begin

    inherited;

    ShowMessage('TMyObject1:' + inttostr(Value));

end;

 

procedure TForm1.Button2Click(Sender: TObject);

type

    TWndMethod = procedure(Value: Integer) of object;

var

    SomeMethod: TWndMethod;

    AMyObject: TMyObject1;

    Msg: TMessage;

begin

    AMyObject := TMyObject1.Create;

    try

        SomeMethod := AMyObject.TestMethod; //--赋值后SomeMethod包含TestMethod AMyObject的指针

        //SomeMethod := TMyObject1.TestMethod;  //--错误!不能用类引用。

        SomeMethod(1); //--执行方法

    finally

        AMyObject.Free;

    end;

end;

说明:

TWndMethod 是一种对象方法类型,它指向一个接收Integer) 类型参数的过程,但它不是一般的静态过程,它是对象相关(object related)的。TWndMethod 在内存中存储为一个指向过程的指针和一个对象的指针,所以占用8个字节。TWndMethod类型的变量必须使用已实例化的对象来赋值。

如果把 TWndMethod变量赋值给虚方法,这时,编译器实现为 SomeMethod 指向AMyObject对象虚方法表中的TestMethod 过程的地址和AMyObject对象的地址。也就是说编译器正确地处理了虚方法的赋值。调用 SomeMethod(1) 就等于调用AMyObject.TestMethod (Message)

在可能被赋值的情况下,对象方法最好不要设计为有返回值的函数(function),而要设计为过程(procedure)。原因很简单,把一个有返回值的对象方法赋值给TWndMethod 变量,会造成编译时的二义性。

 

示例:方法指针指向对象方法

代码:

type

    TMyObject = class(TObject)

    public

        procedure Method1(Sender: TObject);

    end;

 

procedure TMyObject.Method1(Sender: TObject);

begin

    ShowMessage('123');

end;

 

procedure TForm1.Button2Click(Sender: TObject);

type

    TMyMethod = procedure(Sender: TObject) of object;

var

    AMyMethod: TMyMethod;

    AMyObject: TMyObject;

begin

    AMyObject := TMyObject.Create;

    try

        AMyMethod := AMyObject.Method1;

        AMyMethod(AMyObject);

    finally

        AMyObject.Free;

    end;

end;

 

示例:方法指针指向过程/函数

说明:

1)、不能使用赋值语句“对象方法指针 =  @过程/函数 ,因为对象方法指针和过程指针(“@过程/函数”返回一个过程指针)类型不同,它们各自的空间大小也不同,普通的过程指针不包含对象指针,所以不能用于给对象方法指针类型赋值。

2)、因为Delphi中给对象方法指针赋值必须加上对象实例,如TestObj.Hello才行,一种投机的方法是可以通过TMethod将对象方法指针重定向到一个全局函数

3)、由于对象方法包括一个隐含对象指针参数Self,所以定义过程/函数时必须加入一个假参数const pSelf: Pointer以保证对象指针self能够被传入。

代码:

type

    TMyObject = class(TObject)

    private

        FTestEvent: TNotifyEvent;

    public

        procedure ExecTestEvent(Sender: TObject);

        //---

    published

        property TestEvent: TNotifyEvent read FTestEvent write FTestEvent;

    end;

 

//--类方法隐藏了第一个参数为对象的 Self (放在EAX中传递)

//--故第一个参数为 Self: TObject,第二个参数对应 TNotifyEvent 的参数

procedure Gproc(Self: TObject; Sender: TObject);

begin

    ShowMessage('Test');

end;

 

procedure TMyObject.ExecTestEvent(Sender: TObject);

begin

    if Assigned(FTestEvent) then

        FTestEvent(Sender);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    M: TMethod; //--方法记录

    AMyObject: TMyObject;

begin

    with M do

    begin

        Code := @Gproc; //--指向方法(这里是全局过程)地址

        Data := nil; //--指向类实例(该值对应着 Gproc 调用时的 Self 参数值,可为任意值,这里传nil

    end;

    //---

    AMyObject := TMyObject.Create;

    try

        AMyObject.TestEvent := TNotifyEvent(M);

        AMyObject.ExecTestEvent(nil);

    finally

        AMyObject.Free;

    end;

end;

说明:

1)、第一个参数Self: TObject;绝对不是多余的,虽然你不用它编译也能通过,还可能执行正确,但如果你想在过程中调用对象之真的话,没有它可不行。

Delphi默认使用寄存器加堆栈的方式传递过程参数,三个参数以内使用EAXEDXECX的顺序保存参数,三个以后用堆栈。对类的方法来说,EAX恒定是类实例指针Self,这样Sender参数实际上放在EDX中做为第二个参数传递,如下面的代码:

procedure MyMouseDown(Self: TObject; Sender: TObject; Button: TMouseButton;

  Shift: TShiftState; X, Y: Integer);

begin

  ShowMessage(Format('Self: %d, Sender: %d, X: %d, Y: %d', [Integer(Self),

    Integer(Sender), X, Y]));

end;

procedure TForm1.FormCreate(Sender: TObject);

var

  M: TMethod;

begin

  M.Code := @MyMouseDown;

  M.Data := nil;

  Self.OnMouseDown := TMouseEvent(M);

end;

上面的代码:

Self     -->  EAX

Sender  -->  EDX

Button  -->  CL    // TMouseButton枚举类型其实内部作Byte

其它三个放堆栈。

如果把Self: TObject去掉,则MyMouseDown认为只有最后两个参数放在堆栈,返回时POP EIP得到的是错误的指针,你将看到一个非法访问内存错。

2)、M.Data := nil;我指的是语法上、运行时可为任何值而没有错误。

M.Data保存了方法所对应的对象指针,用于在调用该方法时提供Self这个隐藏参数,在普通过程中其实是没有用的。难道你还想在过程中用Self来访问一个按钮或窗体吗?M.Data并不是SenderSender是由方法(这里是过程)调用方指定的。例如VCL源码:

procedure TControl.DblClick;

begin

  if Assigned(FOnDblClick) then FOnDblClick(Self);

end;

你修改M.Data来作为Sender其实是没用的。

 

3)、定义M: TMethod只需要赋值时做一次强制类型转换。Delphi的类型强制转换要求类型相容或长度一致,TMethod与对象方法指针是相容的(8字节),事实上VCL在调用事件时,也是按TMethod的结构来调用的,而TNofityEvent等类型在本质上与TMethod结构相同,但在Delphi这种强类型语言中使用它们可以避免很多错误。

 

 

示例:方法指针指向过程/函数

代码:

procedure TestMethod(const pSelf: Pointer; AData: Integer);

begin

    ShowMessage(IntToStr(AData));

end;

 

procedure TForm1.Button2Click(Sender: TObject);

type

    TMyMethod = procedure(AData: Integer) of object;

var

    AMyMethod: TMyMethod;

begin

    with TMethod(AMyMethod) do

    begin

        Code := @TestMethod;

        Data := nil;

    end;

    //---

    AMyMethod(1);

end;

 

示例:方法指针指向过程/函数

代码:

type

    TMyObject = class(TObject)

        procedure ExecMethod(const AMsg: string);

    end;

 

procedure TMyObject.ExecMethod(const AMsg: string);

begin

    ShowMessage(AMsg);

end;

 

procedure TestMethod(pSelf: TObject; const AMsg: string);

begin

    ShowMessage(AMsg);

    if pSelf is TMyObject then

        TMyObject(pSelf).ExecMethod(AMsg);

end;

procedure TForm1.Button2Click(Sender: TObject);

var

    AMyObject: TMyObject;

begin

    AMyObject := TMyObject.Create;

    try

        TestMethod(AMyObject,'123');

    finally

        AMyObject.Free;

    end;

end;

代码:

type

    TFakeEvent = procedure(const AMsg: string) of object;

 

    TMyObject = class(TObject)

    private

        FTestEvent: TFakeEvent;

    public

        constructor Create;

        //---

        procedure ExecTestEvent(const AMsg: string);

        procedure ExecMethod(const AMsg: string);

    end;

 

procedure TestMethod(pSelf: TObject; const AMsg: string);

begin

    ShowMessage(AMsg);

    if pSelf is TMyObject then

        TMyObject(pSelf).ExecMethod(AMsg);

end;

 

constructor TMyObject.Create;

begin

    with TMethod(FTestEvent) do

    begin

        Code := @TestMethod;

        Data := self;

    end;

end;

 

procedure TMyObject.ExecTestEvent(const AMsg: string);

begin

    if Assigned(FTestEvent) then

        FTestEvent(AMsg);

end;

 

procedure TMyObject.ExecMethod(const AMsg: string);

begin

    ShowMessage(AMsg);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    AMyObject: TMyObject;

begin

    AMyObject := TMyObject.Create;

    try

        AMyObject.ExecTestEvent('123');

    finally

        AMyObject.Free;

    end;

end;

 

示例:方法指针调用过程/函数

代码:

type

    TFakeEvent = procedure(const AMsg: string) of object;

 

    TMyObject = class(TObject)

    private

        FTestEvent: TFakeEvent;

        procedure TestEvent(const AMsg: string);

    public

        constructor Create;

        //---

        procedure ExecTestEvent(const AMsg: string);

    end;

 

procedure TestMethod(const AMsg: string);

begin

    ShowMessage(AMsg);

end;

 

constructor TMyObject.Create;

begin

    FTestEvent := self.TestEvent;

end;

 

procedure TMyObject.ExecTestEvent(const AMsg: string);

begin

    if Assigned(FTestEvent) then

        FTestEvent(AMsg);

end;

 

procedure TMyObject.TestEvent(const AMsg: string);

begin

    TestMethod(AMsg);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

var

    AMyObject: TMyObject;

begin

    AMyObject := TMyObject.Create;

    try

        AMyObject.ExecTestEvent('123');

    finally

        AMyObject.Free;

    end;

end;

 

*对象指针(代码)

示例:方法指针的结构

说明:DELPHI中的对象是是一个32位的指针

代码:

procedure TForm1.Button2Click(Sender: TObject);

var

    AObject: TObject;

begin

    AObject := TObject.Create;

    try

        Self.Memo1.Lines.Add('对象指针大小:' + IntToStr(SizeOf(AObject)));

    finally

        AObject.Free;

    end;

end;

 

 

示例:对象指针的地址

代码:

procedure TForm1.Button2Click(Sender: TObject);

var

    pEdit: ^TEdit;

begin

    pEdit := @Edit1;

    //---

    with Self.Memo1.Lines do

    begin

        Add('对象内容:' + IntToStr(Integer(Edit1)));

        Add('对象指针的内容:' + IntToStr(Integer(pEdit)));

        Add('对象指针指向的内容:' + IntToStr(Integer(pEdit^)));

    end;

    //---

    pEdit^.Text := '123';

    pEdit.Text := '123'

end;

说明:

在这里还有一个有趣的现象,我们不但可以用“Edit1^.Text”还可以用“Edit1.Text”方式访问对象的属性和方法,其结果是一样的,所以在Delphi中只要是对象都是按指针实现的。

 

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多