开放数组的非引用计数问题这个问题非常难缠,昨天调试了一个晚上才定位到问题。今天用最简单的方式把问题重新模拟一番。
说明:我有两个list,两个list都是存放array of byte类型数据。其中一个list用于获取数据;另外一个list是对传入数据list内容的拷贝。Type 
  TByteArr = array of Byte;  {自定义结构}
  TMyList = class
  private
    F_lstData : TList; // 类型:TList< TByteArr >
  private
    function GetAData( index : Integer ) : TByteArr; 
  public
    {加入一条数据}
    procedure Add( data : TByteArr );
  property Items[ index : Integer ] : TByteArr read GetAData;
  end;  function TMyList.GetAData( index : Integer ) : TByteArr;
  begin
    Assert( (index >= 0 ) and (index < F_lstData.Count) );
    result := TByteArr(F_lstData.Items[i]); // (1)
  end;
 
  procedure TMyList.Add( data : TByteArr );
  var
    duplicate : TByteArr;
  begin
    duplicate := Copy(data); // (2) 说明,不使用开放数组引用计数,我自己做拷贝
    F_lstData.Add( duplicate );
  end;
// 应用程序
var 
  lst  : Tlist;
  ml   : TMyList;
  bytes1 , bytes2 : TByteArr;
  peek  : TByteArr;
  i : Integer;
begin
  SetLength( bytes1, 10 );
  for i := 0 to High(bytes1) do 
     bytes1[i] := i;
  
  SetLength( bytes2, 7 );
  for i := 0 to High(bytes2) do 
     bytes2[i] := 2;  lst.Add( bytes1 );
  lst.Add( bytes2 );
  Showmessage( IntToStr( length(bytes1) ) ); // 显示10  for i := 0 to lst.count-1 do 
    ml.Add(TByteArr(lst.Items[i]));  peek := ml.Items[0]; // (3) 得到的值总是越界的!!!!
  Showmessage( IntToStr( length(peek) ) );   lst := Tlist.Create;
  lst.Free;
  ml.Free;
end;

解决方案 »

  1.   

    将(2)处的函数改为:procedure TMyList.Add( data : TByteArr );
    begin
      F_lstData.Add( data );
    end;// (3) 处peek的值为0123456789,ok!!!
    问题,我不使用引用计数,自己对开放数组做copy,怎么就不行.
    后面使用:
    SetLength(duplicate, Length(data));
    for i := 0 to High(data) do
      duplicate[i] := data[i];
    还是不行.看来不是copy的问题.
    delphi让人看不透,真是失望!!!
      

  2.   

    看不懂了?和同事反复讨论了很多,得出的结论是array of basetype类型(动态数组)并不是解析成普通的指针(实际上也不是普通的物理指针).因为动态数组是可以不用手工释放的,其释放机智是通过引用计数来的.类似于c++ boost库里的shared_ptr(共享指针).c++ boost库里的shared_ptr本身不是物理指针,其定义大致如下
    template<class Type>
    class shared_ptr

      Type* m_pointer;
      int   m_nRefCount; // 引用计数
    };让链表单独管理共享指针指向的内存是不行的,因为共享指针本身就不能用链表来存储.
      

  3.   

    如果采用容器 array of Type来代替TList,那么程序就ok.但是,这样的方式还是麻烦了.这个问题的确有点难,可以参考boost库的共享指针,揣测大致实现吧.delphi没有摸板,不是很爽啊.不知道有没有人有更好解释.希望这100分不要浪费了.
      

  4.   

    var 
      lst  : Tlist;//--不知道(也许我多嘴,:-))楼主是否以为在这里已经创建了你所定义的对象的实例了呢?
      ml   : TMyList;//---此处也是一样。
    注:在C++中,确实是你定义时,编译器也就分配内存了,存在了这个实例对象,但在D中不是这样的,在D中,动态对象要由程序员自己来动态创建的。在这里,只是定义了两个对象指针罢了。
    呵~~
      bytes1 , bytes2 : TByteArr;
      peek  : TByteArr;
      i : Integer;
    begin  SetLength( bytes1, 10 );
      for i := 0 to High(bytes1) do 
         bytes1[i] := i;
      
      SetLength( bytes2, 7 );
      for i := 0 to High(bytes2) do 
         bytes2[i] := 2;
    //-----------------楼主,我看不到你在哪儿创建两个列表类(lst,ml)的对象实例呢?
      lst:=tlist.create;
      ml:=TMyList.caret;//---就算是这样操作。但楼主自定义的类TMyList中并没有提供构造方法constructor CREATE;也就是说ML的私有域 F_lstData : TList; // 类型:TList< TByteArr >
    并非是一个存在的对象,它仍然是一个“悬浮指针”罢了。
      lst.Add( bytes1 );
      lst.Add( bytes2 );
      Showmessage( IntToStr( length(bytes1) ) ); // 显示10  for i := 0 to lst.count-1 do 
        ml.Add(TByteArr(lst.Items[i]));  peek := ml.Items[0]; // (3) 得到的值总是越界的!!!!
      Showmessage( IntToStr( length(peek) ) );   lst := Tlist.Create;//---你在这儿创建对象实例做什么用?
      lst.Free;
      ml.Free;
    end;我所说的,仅从楼主的代码所得,不对之处,请楼主自己更正了。
      

  5.   

    procedure TForm1.Button1Click(Sender: TObject);
     var
       list:Tlist;
       p,pp:^integer;
       pa:array of byte;begin
       list:=tlist.Create;
       new(p);
       try
        p^:=10;
        list.Add(p);
        setlength(pa,10);
        FillChar(char(pa[0]),10,'a');
        list.Add(pa);
        pp:=list.Items[0];
        showmessage(inttostr(PP^));
        pp:=list.Items[1];
        showmessage(inttostr(ord(pa[0])));
       finally
        dispose(p);
        list.free;
       end;end;
    这是我的测试代码,并不报错的。呵~~,仅供参考。
      

  6.   

    由于逐字逐句敲,没来得及仔细验证,输入打错了.(唉,公司电脑里的代码不能拷贝)
    这里想要说明的是:不能使用TList存储动态数组,这样做非常危险.// 应用程序
    var 
      lst  : Tlist;
      ml   : TMyList;
      bytes1 , bytes2 : TByteArr;
      peek  : TByteArr;
      i : Integer;
    begin
      lst := TList.Create;
      ml := TMyList.Create;  SetLength( bytes1, 10 );
      for i := 0 to High(bytes1) do 
         bytes1[i] := i;
      
      SetLength( bytes2, 7 );
      for i := 0 to High(bytes2) do 
         bytes2[i] := 2;  lst.Add( bytes1 );
      lst.Add( bytes2 );
      Showmessage( IntToStr( length(bytes1) ) ); // 显示10  for i := 0 to lst.count-1 do 
        ml.Add(TByteArr(lst.Items[i]));  peek := ml.Items[0]; // (3) 得到的值总是越界的!!!!
      Showmessage( IntToStr( length(peek) ) );   lst.Free;
      ml.Free;
    end;这个帖子看来分数没人能得到了.
      

  7.   

    莫愁前路无知已,天下谁人不识君?
    确实使用TList存储动态数组非常危险,这道题也特别难,但这100分我拿定了。
    我将你的类做了一下改造,加入了计数操作,看代码吧
    (由于CSDN贴出的代码会变引难看,因此我将两个每两个半角空格替换成了一个全角空格,大家试的时候注意替换回来):TByteArrList类,也即原来的TMyList类:
    -----------------------------------------------------------------------
    unit ByteArrayList;interface
    uses
     SysUtils, Classes;Type
     TByteArr = array of Byte;TByteArrList = class
    private
     F_lstData : TList; // 类型:TList< TByteArr >
    private
     function GetAData( index : Integer ) : TByteArr;
    public
     constructor Create;
     destructor Destroy; override; {加入一条数据}
     procedure Add( data : TByteArr );
     property Items[ index : Integer ] : TByteArr read GetAData; default;
    end;implementation
    function TByteArrList.GetAData( index : Integer ) : TByteArr;
    begin
     Pointer(result) := F_lstData.Items[index];
     Inc( PInteger(PChar(result)-8)^ ); // 手工增加计数
    end;procedure TByteArrList.Add( data : TByteArr );
    begin
     F_lstData.Add( Pointer(data) );
     Inc( PInteger(PChar(data)-8)^ ); // 手工增加计数
    end;constructor TByteArrList.Create;
    begin
     inherited;
     F_lstData := TList.Create;
    end;destructor TByteArrList.Destroy;
    var
     data : TByteArr;
     i: integer;
    begin
     // 需要回收内存
     for i:=0 to F_lstData.Count-1 do
     begin
      Pointer(data) := F_lstData[i];
      data := nil; // 借用Delphi的自动回收技术
     end;
     F_lstData.Free; inherited;
    end;end.
    -----------------------------------------------------------------------主程序,用的是命令行/控制台方式的,你的ShowMessage都改成了Writeln:
    -----------------------------------------------------------------------
    program ByteArrayListTest;{$APPTYPE CONSOLE}uses
     SysUtils,
     ByteArrayList in 'ByteArrayList.pas';procedure main();
    var
     m1, m2 : TByteArrList;
     bytes1 , bytes2 : TByteArr;
     peek : TByteArr;
     i : Integer;
    begin
     m1 := TByteArrList.Create; SetLength( bytes1, 10 );
     for i := 0 to High(bytes1) do
      bytes1[i] := i; SetLength( bytes2, 7 );
     for i := 0 to High(bytes2) do
      bytes2[i] := 2; Writeln( IntToStr( length(bytes1) ) ); // 显示10
     Writeln( IntToStr( length(bytes2) ) ); // 显示7 m1.Add( bytes1 );
     m1.Add( bytes2 ); bytes1 := nil; // 马上销毁好调试
     bytes2 := nil; // 马上销毁好调试 peek := m1.Items[0];
     Writeln( IntToStr( length(peek) ) );
     peek := nil; // 马上销毁好调试 m2 := TByteArrList.Create;
     m2.Add( m1[0] );
     m2.Add( m1[1] );
     peek := m2.Items[1];
     Writeln( IntToStr( length(peek) ) );
     peek := nil; // 马上销毁好调试
     m2.Free; m1.Free;
    end;begin
     main();
     write( 'press ENTER to quit...' );
     readln;
    end.
    -----------------------------------------------------------------------输出是:
    -----------------------------------------------------------------------
    10
    7
    10
    7
    -----------------------------------------------------------------------
    当然,大家还可以加入更多测试,比如看peek的内容对不对。--
    http://agui.net.googlepages.com
    mailto: agui.cn @ gmail.com
      

  8.   

    agui,这帖子我给你100分,你的代码经过测试和内存泄露检查都没问题.不过结贴前,我还是有点不明白,想向你请教:
    Inc( PInteger(PChar(data)-8)^ ); // 这句话如何理解,只知道一串的强制类型转化.还有想问的是引用计数的数据存放在哪里,我知道boost库一点皮毛,它里面是用一个refCount的整形变量来存放引用次数的.delphi里它是如何实现的?最后一个问题:
    Pointer(data) := F_lstData[i]; // 难道等同于 data := Pointer(F_lstData[i]);谢谢.
      

  9.   

    据我所知,动态数组(你说成开放数组,其实还是有一点小差别的)的机制为:
    1、动态数组实体的格式为:
      引用计数: 4bytes
      长度: 4bytes // 元素个数
      内容: 长度*元素大小2、动态数组变量实际上是一个指针,指向的位置为“内容”开始处;3、之所以用PChar(data)先转型,是因为在Delphi中只有Pchar可以和整数运算,实际上用整数来转型也是可以的: integer(data)。PChar(data)-8 取得计数器的地址,PInteger(PChar(data)-8)^ 将计数器的值取出来,Inc() 当然就是加一了。4、data的类型为 TByteArr (动态数组),按照Delphi的语法要进行数组方式的操作的,比如改变计数值,所以必须要转型为Pointer。另外,data := Pointer(F_lstData[i]) 估计要出编译错(Pointer值赋给TByteArr变量),而且 F_lstData[i] 本身就是 Pointer 类型,不需要转换。如果写成 data := TByteArr(F_lstData[i]) 就会引起一系列内部操作,导致错误。使用Pointer是为了我们自己可以控制不会有其它操作发生。有什么问题可以来邮 agui.cn @ gmail.com
      

  10.   

    agui说的很精辟.这一百分全给了.