var
  p:pchar;procedure TForm1.Button1Click(Sender: TObject);
var
  s:string;
begin
  s:='aaa';
  p:=PChar(s);
end;procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowMessage(p);//-->结果为'aaa';
end;以上,p为全局变量.s为局部变量,理论上在TForm1.Button1Click(Sender: TObject)作用域结束后,s占用的内存应该清空.为何在TForm1.Button2Click(Sender: TObject)中还能显示出'aaa'???

解决方案 »

  1.   

    Pchar不是指针?书上定义:Pchar 指向null结束的char字符串的指针
      

  2.   

    pchar所占的地址空间是可以分配,赋值的,可以看成变量
      

  3.   

    s在Button1Click过程结束之后是已经被释放掉了,P指向的是一个野指针.只是由于没有后续代码重新申请原来S分配的内存,并加以改写,所以'aaa'仍然存在.
      

  4.   

    TO 僵哥(不需要告诉别人你是否初学,求怜不如思进取)问题似乎没那么简单吧。'aaa'是一个常量。常量是不存在释放问题的。所以,后面无论怎么分配内存,p都是有效的,并且一直会是aaa
      

  5.   

    在这个示例当是'aaa'这是一个常量串,但是关键点在于楼主写的代码不是 p := 'aaa';,而是p := PChar(s);,所以应该抛开'aaa'这个来源.在正常使用过程当中,并不能保证s 在赋值给p之前不被改写.
      

  6.   


    呵呵,这是不能抛开的。
    “只是由于没有后续代码重新申请原来S分配的内存,并加以改写,所以'aaa'仍然存在.”
    你可以验证一下,无论你怎么重新分配,都无法使p无效。因为,s仅仅是对这个常量指针的引用。
    换句话说
    procedure TForm1.Button1Click(Sender: TObject); 
    var 
      s:string; 
    begin 
      s:='aaa';  //这里s不会分配内存,并复制aaa
      p:=PChar(s); //而这里,实际就是将p直接指向了常量内存
    end; 
    或者你可以再作个测试:
    procedure TForm1.Button1Click(Sender: TObject); 
    var 
      s:string; 
    begin 
      s:='aaa';  
      p:=PChar(s);   ShowMessage(IntToStr(Integer(p)));  
      s[1] := 'b';  p := PChar(s);
      ShowMessage(IntToStr(Integer(p)));
    end; 
    你会发现2次ShowMessage的值是不 一样的。
    第一个ShowMessage的地址显示是$004XXXXX 也就是在进程的地址空间(说明:没修改Image Base的情况下)
    而第二个ShowMessage的地址显示则是一个与$004XXXXX差别很大的地址.
    之所以产生这样结果的原因是 s[1] := 'b'导致了 s进行了值拷贝,也就是重新分配了内存,并将原内存的内容复制过来了.这些操作是由string的内部实现完成的.
      

  7.   

    不好意思,16进制地址显示,应该用这个:
    ShowMessage(IntToHex(Integer(p), 8));
      

  8.   

    如果你硬是要从楼主的这人代码片段去挑问题,那我没办法.如果我说是因为 p此时指向的是 常量 'aaa',所以永远存在不会被释放.万一哪天楼主把代码变成了:
    SetString(s,PChar('aaa'),3);//相当于一个复制操作
    然后发现结果不是我所说的.那楼主不又要来找我麻烦了?
      

  9.   

    其实按照8楼的说法,是无法去解释'aaa'是一个常量的.除非你找出Delphi程序当中常量存储基地址.而这个得从程序初始化内存布局开始讲,问题就不是一点点复杂度.而且这个问题的适用范围非常小.
      

  10.   

    这并不是我在挑问题,我说的只不过是问题的本质.并且我认为你说的不正确,需要纠正."在Button1Click过程结束之后是已经被释放掉了,P指向的是一个野指针.只是由于没有后续代码重新申请原来S分配的内存,并加以改写,所以'aaa'仍然存在."楼主可能马上就会找你:"我有没有后续代码重新申请原来S分配的内存,并加以改写",但是aaa还是存在.
      

  11.   

    'aaa'本就是个常量,这无须解释.s := 'aaa'在内存中,仅是修改了s内部的指针,指向了该常量.当对s进行修改时,s会新分配一块内存,并复制原指针内容,这是string内部如此实现的.
    当然对于一般使用者,是感觉不到这种区别的.
      

  12.   

    这是为了简化问题的说明.之所以这里指向的是一个常量地址,那是因为S使用的是一个常量的地址,这是由于s的Copy on Write机制决定的,但是这里p是由s传递过来的,S的释放,我就可以认为P指向的是一个野指针.野指针的概念,并不是说这个指针指向的数据是否有效,而是说这个指向的内容是否合法,或者叫符合设计逻辑.s是一个字符串,同时也可以称之为管理一段字符数据内存的指针引用.实际上s需要向内存管理器申请一块有效的内存.即使是这里面的常量'aaa',实际上也对应着这样的一段内存,只是它的生命周期贯穿于整个进程的生命周期.而当s释放就存在两种情况,如果它引用的是一个已经存在的String对象,那它释放的时候,仅只是释放该String对象上的一个引用计数,而如果引用的是一段完全独立的内存(String对象的引用计数仅为1),那就会把String对象本身分配出来的内存也Free掉.Free的概念就是告诉内存管理器,该段内存已经不再做管理,而可以分配给别人使用.而象P这样子不受管理和约束的对象,就有点象一个失去约束的非法分子.随时都有可能做出破坏.所以称之为野指针.
      

  13.   

    楼主,不好意思,我把你搞糊涂了.其实你的问题如果换成是这样的
    var 
      p:PInteger; procedure TForm1.Button1Click(Sender: TObject); 
    var 
      s:Integer; 
    begin 
      s:= 125; 
      p:= @s; 
    end; procedure TForm1.Button2Click(Sender: TObject); 
    begin 
      ShowMessage(IntToStr(p^));//-->结果为125; 
    end; 那么 僵哥(不需要告诉别人你是否初学,求怜不如思进取)是完全正确的.但是涉及到string这种类型就有点区别了. 你暂时可以按 僵哥 的意思来理解.
      

  14.   

    僵哥说p是一个野指针是完全正确的,常量的例子如下procedure TForm1.Button1Click(Sender: TObject);
    const a='aaa';
    var
      s:string;
      p1,p2,p3:PChar;
    begin
      s:='aaa';//s分配内存,从常量,赋值'aaa'
      p1:=pchar(s);//p1肯定是一个野指针
      p2:=pchar(a);//p2指向a的指针
      p3:=pchar('aaa');//p3也是指向a的指针,因为aaa本身和常量a解释器认为是同一个指针的引用end;
      

  15.   

    呵呵,你可以测试下:procedure TForm1.Button1Click(Sender: TObject);
    var
      s: string;
      p: PChar;
    begin
      s := 'abc';
      p := PChar(s);  p^ := 'c';
      ShowMessage(p);
    end;这个是会崩溃的.
      

  16.   


    呵呵,你可以测试下: procedure TForm1.Button1Click(Sender: TObject); 
    var 
      s: string; 
      p: PChar; 
    begin 
      s := 'abc'; 
      p := PChar(s);   p^ := 'c'; 
      ShowMessage(p); 
    end; 这个是会崩溃的.
      

  17.   


    小伙子,你最好验证下哟.
    procedure TForm1.Button1Click(Sender: TObject);
    const a='aaa';
    var
      s:string;
      p1,p2,p3:PChar;
    begin
      s:='aaa';//s分配内存,从常量,赋值'aaa'
      p1:=pchar(s);//p1肯定是一个野指针
      p2:=pchar(a);//p2指向a的指针
      p3:=pchar('aaa');//p3也是指向a的指针,因为aaa本身和常量a解释器认为是同一个指针的引用  ShowMessage(IntToHex(Integer(p1), 8) + ' ' + IntToHex(Integer(p2), 8) + ' ' + IntToHex(Integer(p1), 8));
    end;Delphi 7 测试输出:
    00452A48 00452A4C 00452A48
      

  18.   

    不过感谢你  ideation_shang,
    让我知道了
    s := 'aaa'和 p3 := pchar('aaa') 是一个地址.
      

  19.   

    蒲石,你搞错了,
    不应该是 
    ShowMessage(IntToHex(Integer(p1), 8) + ' ' + IntToHex(Integer(p2), 8) + ' ' + IntToHex(Integer(p1), 8)); 
    应该是
    ShowMessage(IntToHex(Integer(p1), 8) + ' ' + IntToHex(Integer(p2), 8) + ' ' + IntToHex(Integer(p3), 8)); 
      

  20.   


    哦,的确错了,不好意思.
    Delphi 7 测试输出: 
    00452A48 00452A4C 00452A4C
      

  21.   

    因为p2,p3都是指针,是可控的,所以并用一个串,而s是不可控的,所以另外生成一个独立的串.
    可以试一下把p2的赋值往前移,这样子就更加明显了
    const a='aaa';
    var
      s:string;
      p1,p2,p3:PChar;
    begin
      p2:=pchar(a);//p2指向a的指针
      s:='aaa';//s分配内存,从常量,赋值'aaa'
      p1:=pchar(s);//p1肯定是一个野指针
      p3:=pchar('aaa');//p3也是指向a的指针,因为aaa本身和常量a解释器认为是同一个指针的引用  ShowMessage(IntToHex(Integer(P1),4));
      ShowMessage(IntToHex(Integer(P2),4));
      ShowMessage(IntToHex(Integer(P3),4));
    end;
      

  22.   

    我在Delphi 2009中测试,一样崩溃.
      

  23.   


    原因很简单,常量不能写. s := 'abc'仅是赋予s一个常量指针,并没有分配内存,并复制数据.
      

  24.   

    常量也需要占用内存,并不会非法,关键在于内存分配的位置是否可写.我这里用的是Delphi2007.
      

  25.   

    也就是writeable memory和readonly(writeless) memory的区别.
      

  26.   

    关于"野指针",这个概念或许会有不同的理解.那么有不同理解也许也讨论不出一个结果.关键是
    "只是由于没有后续代码重新申请原来S分配的内存,并加以改写,所以'aaa'仍然存在."
    显然就楼主的代码来看是不对的.
      

  27.   

    编译器把串放到代码段去,由于代码段是只读的,有写入保护,就会产生写入非法的Exception
      

  28.   

    "常量也需要占用内存,并不会非法,关键在于内存分配的位置是否可写.我这里用的是Delphi2007."其实常量写会不会非法我并不关心.关键是 s := 'aaa'并不会分配内存.
    紧接着的 p := PChar(s) 实际就是就该常量指针赋给了 p所以 p指针所指向内存并非不确定的.最后,我的一个猜测 常量地址可写,有可能是Delphi2007的一个BUG  ^_^,常量可写还叫啥常量, 都用变量不就得了.
    还好Delphi 2009又不可以写了.我也记得我用2007写测试程序时发现常量可写,当时百思想不得其解.
      

  29.   

    我只解释 p := pchar(s)之后的这一部分.我认为楼主的这个'aaa'只是一个随机赋值过来的数据.前面已经说过了,我不可能从头到尾把所有String的Copy on Write机制说一遍.常量可写是由来已久的.在我所知道的在Delphi5及之前的版本const常量都是可写的.现在的版本仍然可以通过开关设置({$+j}/{$-j})
      

  30.   

    有点明白了
    const a='aaa';
    var
      s:string;
      p1,p2,p3:PChar;
    begin
      s:='aaa';//s是对常量a的一个引用,并未分配内存
      p1:=pchar(s);//p1是一个指向s的指针,但是s是a的引用
      p2:=pchar(a);//p2是一个指向常量a的指针
      p3:=pchar('aaa');//同上
    end;
      

  31.   

    提到{$+j}/{$-j},我的确好象在帮助中瞟到过,你一提我好象有点印象了.OK,楼主.你就按 僵哥(不需要告诉别人你是否初学,求怜不如思进取)的意思理解吧.
    等你经验丰富了之后,再来翻翻这个帖子吧.
      

  32.   

    没有规则数据不可以放到代码段.其实我也只是一个猜测.刚才从朋友那拿到一个程序(也就是你们上面的代码编译出来的),查看了一下报错的时候常量数据就在EIP指向地址临近的位置.正常情况下,应该是把这些数据放到只读数据段当中,或者常量段.
      

  33.   


    s:='aaa';//s是对常量a的一个引用,并未分配内存 
    这个理解是对的,但是你要注意了:
    const a='aaa'; 
    var 
      s:string; 
      p1,p2,p3:PChar; 
    begin 
      s:='aaa';//s是对常量a的一个引用,并未分配内存 
      s[1] := 'a';
      p1:=pchar(s);//p1是一个指向s的指针,但是s是a的引用 
      p2:=pchar(a);//p2是一个指向常量a的指针 
      p3:=pchar('aaa');//同上 
    end; 如果有了红色这句,s 会在这句上分配内存,复制原来'aaa'中的内容到新分配内存中.然后,再修改s[1].
    所以,给你的感觉就像 s:='aaa'时已经分配过一样.
      

  34.   


    确切的来说,我对PE文件结构还不是十分了解,最近正在学着写PE文件结构分析程序.目前在搞导出表.
    但是就我理解来说,代码执行是线性的,比如执行了一条MOV指令, EIP会自动增加,如果常量放在EIP的执行序列中(你说指的EIP附近),那么就需要频繁的JMP来跳过这些区域.
    感觉正常的程序是不会这么做的,而通用的编译器就更不会自动做这些事情了.
      

  35.   

    又有不明白的地方了
    const a='aaa'; 
    var 
      s:string; 
      p1,p2,p3:PChar; 
    begin 
      s:='aaa';//s是对常量a的一个引用,并未分配内存 
      p1:=pchar(s);//p1是一个指向s的指针,但是s是a的引用 --->p1应该是指向'aaa'的指针吧
      p2:=pchar(a);//p2是一个指向常量a的指针 
      p3:=pchar('aaa');//同上 
    end; 
      

  36.   

    s:='aaa'的指针跟const a='aaa'的指针不相同?但const a='aaa'跟pchar('aaa')相同?
      

  37.   

    总的来说,就lz的这个具体代码而言,
    同意蒲石的观点。===========================================
    s:='aaa'的指针跟const a='aaa'的指针不相同?但const a='aaa'跟pchar('aaa')相同?都相同。不过,如果s以后被重新赋值的话,则s的指向会改变。
      

  38.   

    但是现在的结果是p2=p3,p1<>p2,p1<>p3
      

  39.   

    抱歉,看来我在delphi的问题上受了c的干扰。
    说一下我的新观点:
    1、对于下面的楼主的这段代码,'aaa'并非分配在常量数据段里。之所以ShowMessage时还能显示出'aaa',完全是因为这段被回收的空间暂时没有被再次占用。故我赞同僵哥的最初的观点。
    var 
      p:pchar; procedure TForm1.Button1Click(Sender: TObject); 
    var 
      s:string; 
    begin 
      s:='aaa'; 
      p:=PChar(s); 
    end; procedure TForm1.Button2Click(Sender: TObject); 
    begin 
      ShowMessage(p);//-->结果为'aaa'; 
    end; 2、另一段程序的问题:
    s:='aaa'的指针跟const a='aaa'的指针不相同?但const a='aaa'跟pchar('aaa')相同? 
    你的推断是正确的。s与a虽然都是'aaa'但两个分别在变量段和常量段,所以两个指针互不相同。而后边的pchar('aaa')中的'aaa'是常量,恰与a的值相同,故pchar('aaa')指向a所表示的'aaa'的内存地址。做了实验,即使改成s:=a;s与a的地址也不同。3、周爱民的《delphi源代码分析》的第15页有相关的内容,如有可能,你可以参考一下,相信会有收益。4、最后向僵哥和蒲石两位前辈表示敬意,同时再次向lz表示歉意,在你已经快明白的时候,我又把你弄糊涂了 :)
       以上个人观点,欢迎指正。关注楼下能有高手继续让我和lz都有进步。
      

  40.   

    呵呵,ideation_shang也被搞糊涂了。其实:
    const a='aaa'; 
    var 
      s:string; 
      p1,p2,p3:PChar; 
    begin 
      s:='aaa';//s是不是对常量a的一个引用,而是另外分配内存 ,两个一个是变量,一个是常量,分属不同区域,可以看看exe文件内容,可以佐证。
      p1:=pchar(s);//p1是一个指向s的指针,故s不是a的引用 
      p2:=pchar(a);//p2是一个指向常量a的指针 
      p3:=pchar('aaa');//同上 
    end; 
      

  41.   

    我确实糊涂了,本来我出的这个例子就是让问题更容易说明白,谁知道把自己绕进去了.
    const a='aaa'; 
    var 
      s:string; 
      p1,p2,p3:PChar; 
    begin 
      s:='aaa';//这里的'aaa'确实是一个常量,而是一个隐含的常量,但并不是常量a,s是对这个隐含常量的引用
      p1:=pchar(s);//p1是一个指向s的指针
      p2:=pchar(a);//p2是一个指向常量a的指针 
      p3:=pchar('aaa');//p3也是一个指向常量a的指针  
    end; 
    //事实证明,p2和p3都是指向常量a的指针,而p1是指向隐含常量'aaa'的指针。同时也证实了蒲石所说的p1是指向常量的指针的正确性,但是这个常量并不是常量a.