使用move过程复制结构体变量的问题,盼高手解惑。。。(300分)

  • 主题发起人 FreeAndNil
  • 开始时间
F

FreeAndNil

Unregistered / Unconfirmed
GUEST, unregistred user!
我想把一个结构体变量的值拷贝到另外一个结构体,用move过程,结果发现很奇怪的现象。
定义一个结构体变量,和定义一个结构体指针变量,然后new,有什么区别?move过程有哪些要注意?如果要复制结构体内容的代码在dll中会怎样?用sharemem和不用有区别吗?请高手帮忙解下惑,谢谢,只有300大富翁币。

type
TTestFunc =function (Arec: pointer):boolean
stdcall;
TA = record
sD1: string;
sD2: string;
sD3: string;
sD4: integer;
sD5: integer;
sD6: integer;
sD7: string;
end;
TB = record
sD1: string;
sD2: string;
sD3: string;
sD4: integer;
sD5: integer;
end;


procedure TForm1.btn3Click(Sender: TObject);
var
a: TA;
b: Tb;
begin
a.sD1 := edt1.Text ;
a.sD2 := edt2.Text ;
a.sD3 := edt3.Text ;
a.sD4 := ud1.Position;
a.sD5 := ud2.Position;
a.sD6 := ud3.Position;
a.sD7 := edt7.Text ;

move(a, b, sizeof(TB))
//用这句代码,代码只能执行一次,第二次就直接关程序
// b := TB((@a)^)
//用这句代码,edt的内容怎么折腾都不会出错

with b do
ShowMessage('sD1:' + sD1 + ',sD2:'+sD2+',sD3:'+sD3+',sD4:'+IntToStr(sD4)+',sD5:'+IntToStr(sD5));
end;

procedure TForm1.btn4Click(Sender: TObject);
var
a: TA;
b: ^Tb;
begin
a.sD1 := edt1.Text ;
a.sD2 := edt2.Text ;
a.sD3 := edt3.Text ;
a.sD4 := ud1.Position;
a.sD5 := ud2.Position;
a.sD6 := ud3.Position;
a.sD7 := edt7.Text ;

new(b);
move(a, b^, sizeof(TB))
//用指针,再new,然后edt的内容怎么折腾都不会出错,why?

with b^ do
ShowMessage('sD1:' + sD1 + ',sD2:'+sD2+',sD3:'+sD3+',sD4:'+IntToStr(sD4)+',sD5:'+IntToStr(sD5));

// dispose(b)
//奇怪的是,如果不释放指针,代码没有任何问题,一旦加入这句话,第一次,没问题,第二次,什么也不提示,直接关闭程序。
end;
 
楼主的MOVE函数用法有些问题。
MOVE是内存数据拷贝,如果记录中的变量有指针的话,那只是拷贝了指针的地址,并不能拷贝指针指向的内容,这个千万要注意,而记录中有string类型的变量是不能用Move的,用了出错是正常,不出错是不正常的。
 
DLL中参数是不能有直接有string类型的参数的,因为在其他与语言中找不到一个可以和string对应的类型。
如果是结构中有string,那就需要有sharemem单元,而且调用方也必须是Delphi语言编写了,这样的DLL就不通用所有语言了
 
用Char型做DLL中的参数?顶一个
 
>>楼主的MOVE函数用法有些问题。MOVE是内存数据拷贝,如果记录中的变量有指针的话,那只是拷贝了指针的地址,并
>>不能拷贝指针指向的内容,这个千万要注意,而记录中有string类型的变量是不能用Move的,用了出错是正常,不出错是不正常的

你说的好像有点不对,首先,string虽然是指针,但是,在同一个过程中,用move复制了指针,指向的是一样的地址,应该不出错才对。
而且,奇怪是,即使复制指针不对,为什么第一次调用总是正确,而第二次调用,则程序立马崩溃,连个错误都不出?
更奇怪的是,用结构指针,再new,怎么用怎么对,完全没有之前的一切问题(除了内存增长:)),但是一旦加入dispose,同样出现第一调用总是正确,而第二次调用,则程序立马崩溃的情况?
 
指针的错误千奇百怪的,你只要按照正常的代码写,不会有问题,如果你非要按照非正常代码些,就可能出现奇怪的问题。这时候你还要研究这些问题到底是怎么出现的,或则说到底那一步错了,那就只能你自己慢慢研究了。因为我从来Move的结构中,绝对不用string变量,如果是指针,则Move完了再根据需要看是只需要地址复制还是内容复制再做进一步的处理。至于为什么string会有那么奇怪的现象就不清楚了,从来没用过。也许搞清楚了就又对底层更了解了,呵呵,加油吧,楼主,你要搞清楚了,告诉我们一声,一起分享。
 
对了,string虽然底层是指针,但是确实不能简单的看做指针,因为DELPHI编译器对它有特殊处理,完全不同于一般的指针处理。比如string的写拷贝就是典型的特殊处理。
s := s1 + s2;
这样的代码,如果s,s1,s2都是指针,是编译通不过的
s1 := s2;
如果s1和s2定义成指针和定义成string,也是效果不一样的。
 
这个问题跟Move函数没有关系!
跟Delphi变量分配机制有关系.
 
>你说的好像有点不对,首先,string虽然是指针,但是,在同一个过程中,用move复制了指针,指向的是一样的地址,应该不出错才对。
>而且,奇怪是,即使复制指针不对,为什么第一次调用总是正确,而第二次调用,则程序立马崩溃,连个错误都不出?

如果是确实是指针,是不会出这个错的,但确实string不是指针,起码在语言层面,对于编译器来讲。
 
对了,我这么解释一下,楼主看看如何
move(a, b, sizeof(TB))
//用这句代码,代码只能执行一次,第二次就直接关程序

由于string有写拷贝功能,在引用计数为零的时候,释放内存,当执行上一段代码后,强行把a的sizeof(TB)长度内容复制给b,当然a中的string也被复制,这时候复制的是a字符串中的地址。假设当时a的字符串计数器是1,当然,move后b中字符串计数器也是1(同一个内存嘛),当函数结束的时候,由于b是局部变量,编译器自动销毁b中字符串,由于b的计数器为1,计数减1后为零,所以要销毁字符串所占内存,而这个内存其实是别人的(edt的),销毁了,当然edt就会出错。这也解释了为什么new不出错,因为new的变量的内存需要主动销毁,编译器不自动销毁,你不销毁,当然就没错,你一销毁,就把不是你拥有的string内存销毁,别人就完蛋,就出错了。
 
以前只是明白Move的结构中肯定不能有string,从没考虑为什么不能有string。今天这么一看,应该是这个原因。不过我个人认为,这么折腾没有意义。如果能清楚地知道Delhpi是如何处理string的,自然就会避免这个错误。分析这些错误,无非反过来确认string的处理确实是那么一回事而已。而这些知识直接从Delphi的语言书中就很容易的了解到了的
 
使用move拷贝内存,源的大小必须是编译的时候就可以分配内存大小,string大小是不确定的,因此定义string的时候指定长度就行了
 
与其这样Move造成引用计数的错乱,不如直接用PChar来的实在——学学C是怎么传递
struct、传递字符串就是。
 
用array of char不就好了
 
--修改回复,刚才回复鲁莽了。

解决了,是引用记数的问题,这样处理即可解决:

procedure TForm1.btn4Click(Sender: TObject);
var
a: TA;
b: ^Tb;
begin
a.sD1 := edt1.Text ;
a.sD2 := edt2.Text ;
a.sD3 := edt3.Text ;
a.sD4 := ud1.Position;
a.sD5 := ud2.Position;
a.sD6 := ud3.Position;
a.sD7 := edt7.Text ;

new(b);
move(a, b^, sizeof(TB))

inc(pinteger(integer(a.sD1) - 8)^);
inc(pinteger(integer(a.sD2) - 8)^);
inc(pinteger(integer(a.sD3) - 8)^);
inc(pinteger(integer(a.sD7) - 8)^);
with b^ do
ShowMessage('sD1:' + sD1 + ',sD2:'+sD2+',sD3:'+sD3+',sD4:'+IntToStr(sD4)+',sD5:'+IntToStr(sD5));
dispose(b);
end;
 
楼主蛮有专研精神,竟然搞出了解决办法,佩服。
 
刚看到楼主的回复时,我郁闷了一下,我确实没有运行过代码,全在那里纸上谈兵,呵呵,没想到楼主最后用实际代码证明我的瞎猜猜对了。[:D]
楼主有这样的专研精神,终有一日会成为绝顶高手的,如果一直保持的话。
 
过奖了,没那么厉害
 
楼主最后给出的代码针对引用计数进行了处理,不过会在字符串为nil时出现非法内存访
问错误。下面是一段来自《Delphi In A Nutshell 1st Ed》的改变引用计数代码:

procedure StoreAndIncStrRef(const Value: String
PtrAddr: Integer);
var
LocalStr:String;
begin
LocalStr:=Value
//增加引用计数
PChar(PInteger(PtrAddr)^):=PChar(LocalStr)
//用引用计数无关的方式赋给指针
Pointer(LocalStr):=nil
//强制清空LocalStr,避免在退出procedure时减少引用计数
end;

下面是减少引用计数的过程(截取自一段代码的内部——能说明意思即可:p
procedure ClearStrRef;
var
LocalStr:String;
begin
Pointer(LocalStr):=PChar(PInteger(LongWord(FMemBlockPtr)+fd.GetMemOffset)^);
end
//退出过程时,Delphi会减少LocalStr的引用计数

我个人的意见是,解决编程中遇到的问题尽量用这样的策略优先次序:
语言无关的技术 > 语言有关,但版本无关的技术 > 和具体语言具体版本有关的技术
 
顶部