線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:4573
推到 Plurk!
推到 Facebook!

如何用getmem对arry of string类型变量分配内存空间?

答題得分者是:aftcast
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#1 引用回覆 回覆 發表時間:2008-10-24 16:31:25 IP:58.17.xxx.xxx 訂閱
var 
aa:array of string;
begin
getmem(aa,3);
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //为何这里会报错,访问内存地址错误
freemem(aa);
end;

如何用getmem对arry of string类型变量分配内存空间?
还请高手指点,谢谢!
RootKit
資深會員


發表:16
回覆:357
積分:419
註冊:2008-01-02

發送簡訊給我
#2 引用回覆 回覆 發表時間:2008-10-24 17:21:09 IP:61.222.xxx.xxx 訂閱
GetMem 僅申請內存空間(不含結構)。

1. 設定陣列,用 SetLength 。
2. String 為自動管理內存,若真要用 GetMem 可為每一個陣列各別申請,不過多此一舉。
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#3 引用回覆 回覆 發表時間:2008-10-26 13:49:37 IP:220.176.xxx.xxx 訂閱
RootKit大大,您好!
十分感谢您的指点!
如果要用GETMEM为每个阵列分别申请空间,如上例:
var
aa:array of string;
begin
getmem(aa,3);
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //为何这里会报错,访问内存地址错误
freemem(aa);
end;

该如何为每个阵列来申请空间呢?还请指点,谢谢!

===================引 用 RootKit 文 章===================
GetMem 僅申請內存空間(不含結構)。

1. 設定陣列,用 SetLength 。
2. String 為自動管理內存,若真要用 GetMem 可為每一個陣列各別申請,不過多此一舉。
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#4 引用回覆 回覆 發表時間:2008-10-27 01:27:17 IP:122.120.xxx.xxx 訂閱
應該要改成
getmem(aa,12);

因為一個指針需要配4個bytes,你要放三個指針,所以要12bytes。
第0個指針存放著 'abcefg'的內存位址
第1個指針存放著 'adfs'的位址
第3個指針存放著 'adfsa'的位址

我是用cb的人,我想delphi是同理的內存配置概念吧!


===================引 用 like 文 章===================
var
aa:array of string;
begin
getmem(aa,3);
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //为何这里会报错,访问内存地址错误
freemem(aa);
end;

如何用getmem对arry of string类型变量分配内存空间?
还请高手指点,谢谢!
------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#5 引用回覆 回覆 發表時間:2008-10-28 14:29:18 IP:58.17.xxx.xxx 訂閱
aftcast大大,getmem(aa,12)这样分配还是不行。
Rookit大大回答中有一句话“若真要用 GetMem 可為每一個陣列各別申請”,如何把aa当成阵列用getmem来分配呢?

还请指点,谢谢!

===================引 用 aftcast 文 章===================
應該要改成
getmem(aa,12);

因為一個指針需要配4個bytes,你要放三個指針,所以要12bytes。
第0個指針存放著 'abcefg'的內存位址
第1個指針存放著 'adfs'的位址
第3個指針存放著 'adfsa'的位址

我是用cb的人,我想delphi是同理的內存配置概念吧!

hagar
版主


發表:143
回覆:4056
積分:4445
註冊:2002-04-14

發送簡訊給我
#6 引用回覆 回覆 發表時間:2008-10-28 14:48:07 IP:61.218.xxx.xxx 未訂閱
編輯記錄
hagar 重新編輯於 2008-10-28 14:50:48, 註解 無‧
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#7 引用回覆 回覆 發表時間:2008-10-28 16:15:54 IP:58.17.xxx.xxx 訂閱
版主大大,我看了那篇文章,按着文章中所述写了一下:
type
pta=^ta;
ta=array [0..10] of string;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
pa:pta;
begin
GetMem(pa,3*sizeof(string));
pa[0]:='abecd';
pa[1]:='adfka;fjdka;';
pa[2]:='efk;afjda';
end;

执行到对pa[1]这里报和第一贴同样的错误!
还请指点,谢谢!

===================引 用 hagar 文 章===================
http://www.infojet.cz/program/delphi/tips/tip0024.html
?
hagar
版主


發表:143
回覆:4056
積分:4445
註冊:2002-04-14

發送簡訊給我
#8 引用回覆 回覆 發表時間:2008-10-28 17:59:03 IP:210.242.xxx.xxx 未訂閱
試了一下, 用 string 就會產生 AV, 但是用 integer 就不會
但我不知為什麼...

找到一篇: http://help.madshi.net/StringTypes.htm

改成如下是可以的:

type
pta = ^ta;
ta = array [0..10] of string;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
pa: pta;
begin
pa = AllocMem(3 * SizeOf(string));
pa[0] := 'abecd';
pa[1] := 'adfka;fjdka;';
pa[2] := 'efk;afjda';
end;

但其中原理我還是不懂...
編輯記錄
hagar 重新編輯於 2008-10-28 18:05:14, 註解 無‧
hagar 重新編輯於 2008-10-28 18:05:53, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#9 引用回覆 回覆 發表時間:2008-10-28 23:44:39 IP:60.248.xxx.xxx 訂閱
剛忍不住手癢,還是把delphi給裝上去測式…必竟,我個人覺得對memory的管理已經有非常精熟的了解了,怎會有錯呢?!

裝好delphi後,開始測,結果發現了一些compiler的問題,但這些問題容後再談。先把可以run的結果貼出

var
aa:array of string;
begin
getmem(aa,12); //是的,很確定就是我上回的觀念,這是裡12個bytes,你用sizeof(string)也是12,因為就是指針
ZeroMemory(aa,12); //只要多加這一行
//上面那行這是關鍵,在c裡不會有這需要,因為我們慣用pchar這類的型別,而這裡會需要是因為string是一種類
//不是基本的型別,故aa[n]='xxxx'時並不會直接把xxxx的位址直接放入aa[n]裡面。
//事實上compiler是把xxxx的位址和aa[0]的值拿來做string的初始化。
//當getmem(aa,12)時,aa指向一塊12bytes大的地區,但這12byte的內容是亂數的,好比如下
// 00 00 00 00 0c 00 00 00 63 6F 6D 63 ............ (這是16進位的內存dump)
//開始第一個字串放入時,會先準備好 00 00 00 00 (4bytes) 和串字的地址二個參數,然後呼叫ansistring的
//建構式。完成後,那段內存就變成
// 48 3d d5 00 0c 00 00 00 63 6F 6D 63 ............
//上面的48 3d d5 00 是一個位址,指向'abcefg'這個物件! 經排列一下,這個位址是 00d53d48
//但是當第二個字串要放入時,會準備好 0c 00 00 00 和字串的地址,然後呼叫建構式,於是就錯了!
//追入建構子發現它會比對這個初始指針是不是00000000,若不是就出錯。這大概是一種保護機制!!!
//ZeroMemory這個win32 aPI 是用來把內存全都設成00
//此外若宣告成 array of PChar 則根本不用ZeroMemory,因為無需建構,它只會把字串的地址就直接放入aa[0]這個指針。
//所以也就同等的沒有所謂的"保護"
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //?何?里???,???存地址??
ShowMessage(aa[2]);
freemem(aa);
end;

下面是pchar的列子,不用Zeromemory
var
aa:array of PCHAR;
begin
getmem(aa,12);
// ZeroMemory(aa,12);
aa[0]:=pchar('abcefg');
aa[1]:=pchar('adfs');
aa[2]:=pchar('adfsa'); //?何?里???,???存地址??
ShowMessage(aa[2]);
freemem(aa);
end;

此外,為何AllocMem可以成功(但是要配合type間接宣告,如上樓寫的)? 因為AllocMem是不僅會配memory,且會把值設成0

但非常怪的事,若你沒有用type宣告…你直接用我的例子把GetMem換成AllocMem,(感覺沒錯? 因為就不用zeromemory),但又av了!!!!
試下面
var
aa:array of string;
begin
aa := AllocMem(12); //感覺很對吧? 但是…compiler並沒有做出你想要的
//此時compiler 竟然不會把配好內存,然後把配好的位址設給aa,它反而
//先把 aa 的位址加上12這二個參數,然後再呼叫AllocMem,於是 aa 這個指針是0,因為一開始的時候它就是0
//接下來的一行就掛了,因為abcefg將配到一個位址為0的地方,馬上掛!
//當若使用type後,如樓上的寫法,allocmem就如預期的會用一個參數12,然後呼叫Allocmem,然後得到一個位址(非0),再
//設給pa。同樣的寫法 aa := AllocMem(12),但組譯成機器語言時竟不同了…
// 這算是bug嗎? 還是borland compiler 優化過度的現象???或者是?????

aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //?何?里???,???存地址??
ShowMessage(aa[2]);
freemem(aa);
end;


最後補充,其實上面的問題是源自於aa是個陣列變數,並非pointer,所以compiler錯亂了,它沒有照正常的方式來編譯。
你要說compiler有問題,或者也可以說是你自己沒把型別批配好導致它錯亂…所以啊,型別很重要! 修改如下即可正確run。

var
aa:array of string;
begin
POINTER(aa) := AllocMem(12);
//加了Pointer( ) 騙一下compiler,讓它以為aa是一個指針,於是就會正確的編譯出allocmem的呼叫方式
//也就是使用12當參數,然後配好內存後,把指針回設給aa
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa';
ShowMessage(aa[2]);
freemem(aa);
end;

結論: 你可以選第一種GetMem(12) Zeromemory的方式,或者用 Pointer(aa) := AllocMem(12)的方式。當然,若改成PChar的方式則效率更佳!
我很用心的寫這一篇文章,深入內存使用原理,希望能對觀念上有大幫助。然而……就達成題目目的而言,這一切很"無聊"。因為,
其實一開始若設 aa:array [0..2] of string; 那麼根本就不需要搞什麼內存配置,直接就可以用了。若真的需要動態的,那用SetLength(aa,3);即可,不過
不可配合使用freemem(aa),要讓delphi自行去free內存。就算你要的是能夠動態的自行釋放內存,那其實也只有釋放出12bytes(3指針),沒大用途!
你可別想像成那些字串全都釋放喔! 並沒有!!


===================引 用 aftcast 文 章===================
應該要改成
getmem(aa,12);

因為一個指針需要配4個bytes,你要放三個指針,所以要12bytes。
第0個指針存放著 'abcefg'的內存位址
第1個指針存放著 'adfs'的位址
第3個指針存放著 'adfsa'的位址

我是用cb的人,我想delphi是同理的內存配置概念吧!
------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2008-10-28 23:51:48, 註解 無‧
aftcast 重新編輯於 2008-10-28 23:57:55, 註解 無‧
aftcast 重新編輯於 2008-10-28 23:59:22, 註解 無‧
aftcast 重新編輯於 2008-10-29 05:37:51, 註解 無‧
aftcast 重新編輯於 2008-10-29 06:29:01, 註解 無‧
aftcast 重新編輯於 2008-10-29 06:32:17, 註解 無‧
hagar
版主


發表:143
回覆:4056
積分:4445
註冊:2002-04-14

發送簡訊給我
#10 引用回覆 回覆 發表時間:2008-10-29 06:53:26 IP:220.137.xxx.xxx 未訂閱
如同 RootKit 大大在一開始的回覆, 用 SetLength 即可

不過有 aftcast 的詳盡解說, 小弟才能更深入了解! 謝謝!
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#11 引用回覆 回覆 發表時間:2008-10-29 08:13:10 IP:59.120.xxx.xxx 訂閱
這個問題,其實 ...

如果你有用心看過,Delphi 附帶的 Pascal 語法 pdf,你就不會有一堆????

1. 首先,請愛用 Setlength,不然你得自己「模擬」IDE/Compile 的行為(我想,這是找自己麻煩吧)

2. String 變數,其實是一個指標,紀錄指向 String[開頭] 的位置,Short String 才是一個直接變數,本身就是指向 String[開頭] 的位置
所以需要多一次的轉換,也就是說凡是需要傳為位址的,都需要使用 address of Strong[1] or PChar(String[1]) 的方式傳入
如 FillChar(String[1], size, length) <-- 當然空間需要事先宣告出來

3.aa:array of string; 雖然可以自己分配,但你需要做哪些動作,你知道嗎?
如果你不知道,請乖乖用 Setlength,就算你知道,也請用 SetLength (你又不是要寫 Compile,別沒事找事做)

4.String 不是真的的只有 那 4 byte 的空間,其還有一個 Header,所以 只有AllocMem(12); 會有問題,或許你可以暫時騙過 Compiler
但實際使用後,會有預期不到的狀況,就是使用 aa[3] 會破壞 aa[2] 或與 aa[1],因為 String所指到的位址,是記錄字串的開頭,而前面 幾個
Byte 是Header資訊,所以光是宣告 4 x 3 是不夠的,至於Header是什麼,有興趣可以去看該 pdf
而需要做哪些動作,有興趣你可以看反譯組碼

如果不忙,你願意花時間的話,可以幫我們 post 出來

===================引 用 hagar 文 章===================
如同 RootKit 大大在一開始的回覆, 用 SetLength 即可

不過有 aftcast 的詳盡解說, 小弟才能更深入了解! 謝謝!
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#12 引用回覆 回覆 發表時間:2008-10-30 02:58:47 IP:122.120.xxx.xxx 訂閱

[code xml]
請在此區域輸入程式碼
[/code]
關於Syntax的第四點說明,我想有些誤會。

使用AllocMem(12)或是GetMem(12)其實是很安全且正確,僅管它有些"不入流"。其中,關於是不是12bytes,會不會有潛在問題? 我想再稍詳述一下。

vcl的元件都是new出來的,正確講就是create在heap上,而變數其實是一種指標,在delphi稱reference?! 不過都是存放位址的變數。
比如說,Form1這個變數,它放的就是instance的位址。而位址,當然就是4bytes,所以若你用sizeof(Form1) 或sizeof(TObject) 或任意的vcl object,通通會得到4。然而這不表示這個instance真的就是4bytes,一個instance裡最重要佔空間的就是Data Member,因為Menthod是共用的。
舉例來說,有2個string。
string A = "12345";
String B = "asdfasdf";
這二個instance在的內存配置是怎樣的呢? 分為二部份,其1是data member 各自有一份,其2是menthod,這部份的程式碼則是共用的,因為是同一種類別。我喜歡傳統的instance這個字而不用object,最主要就是這個字表達出data是各自的,code的部份是共享的。

那instance到底是有多大呢? 你可以用sizeof( *Form1) (BC的用法)得到,我測是得到760bytes。然而String 到底是多大呢? 可惜無法用*String得到。不過這不重要,我們肯定的知道他應該大於4bytes。說到這裡不能頭昏…我以一個例子來說明會清楚點,自製一個string類別 Qstring來說明:

Class Qstring //用c 語法較熟,故沒用delphi的語法
{
int M1; //資料成員1
int M2; //資料成員2
char * M3; // char* 就是PChar,資料成員3,是個指標
Qstring(Char *sz) { M3 = sz }; //建構式,這部份先不討論大小
};

問題1,這個類別多大? 12bytes (2個int加上一個pointer)。
若我用new的方式來create,如下:
QString qs = new QString("ABC")
問題2,這個qs物件指標有多大? 當然還是4bytes,不會是12bytes。因為qs是指標!
問題3,這個qs所指向的instance(object)有多大? 當然是12bytes!! 不會是12bytes "ABC" bytes。與ABC字串無關,這三個字是create在另外的heap上。
此時M3放的只是"ABC"字串的位址而已。


回到最原始的主題,為何說12是沒錯的? 因為aa算是一個二維陣列,第一維放的本來就是指向string資料的指標,第二維才是string裡的那些字串。
因為原題目是將字串常數設定給陣列,故分解動作是:
1/要先將一維的部份new出來,即放3個指標的空間
2/ 要將3個字串常數的空間new出來。(正確一點是StrRec結構 字串大小的整體空間 其他的data member空間)

其中2的部份因為是"常數字串",故compiler會自動的呼叫AnsiString來建構(當然就含分配空間)。所以第二部份的空間問題我們根本不用管,也管不到,也一定很安全!
而步驟1就是我們用GetMem或是AllocMem,或是SetLeng配出來的,共要配出12個bytes。補充一下,為何sizeof(String)會是4bytes,剛講過了,因為就是指標。

把真實的情形再分解更細一點。
aa[0] = "abcdef";
aa[1]= "1234";
1/ compiler 先取參數1 : aa[0]裡的位址,參數2: "abcedf"的位址,然後呼叫AnsiString( AnsiString * const , const char *),呼叫ok後,當然在heap上就配好且copy好"abcdef"的字串了。此時第一個參數aa[0]裡的位址也變了,被設定成"abcdef"的位址。
2/ compiler 先取參數1 : aa[1]裡的位址,參數2: "1234"的位址,然後呼叫AnsiString( AnsiString * const , const char *),呼叫ok後,當然在heap上就配好且copy好"1234"的字串了。此時第一個參數aa[1]裡的位址也變了,被設定成"1234"的位址。

注意!! 我們會怕步驟1的heap區塊與步驟2的heap區塊衝突嗎? 當然不會,因為是都是系統自己依memory的情形配出來的。若會衝,那delphi/bcb大概可以丟了!

至於syntax說講的header的部份,我想應該是指"abcedf"前面有12個bytes的StrRec結構體,裡面放著字串的長度,被參照的次數,記憶配置大小三個int大小。所以,正確的講AnsiString建構子是會配12bytes(StrRec) "abcedf"(6BYTES) =18BYTES的空間。但…這一切都是compiler自行配出的,所以也不可能衝突。

附帶有趣的一題,AnsiString回傳來的指標是指向字串真正的開始,並非StrRec的開始,當然這是合理的。只有當使出Length這個方法時,才會去StrRec的12bytes的最後4bytes找到長度的記錄。

整個二維的處理過程,我們只負責第1維的3個指向字串位址的指標即可,其他的都是系統全自動化,無需擔心,也無法擔心與管理。故我也才說,最多只能"自行"釋放12bytes的指標空間了!

以上的幾篇,其實我都使用反組譯觀查過了,因要貼一堆的碼並解說組合語言恐要花太多的時間,所以沒用出來。至於上面所述的內容其實有部份瑕疵,如例子中的建構式大小沒算進去,語法可能有些小錯等。但主要是講述重點觀念,細節就先省了。若有需要再深入探討。

===================引 用 syntax 文 章===================

4.String 不是真的的只有 那 4 byte 的空間,其還有一個 Header,所以 只有AllocMem(12); 會有問題,或許你可以暫時騙過 Compiler
但實際使用後,會有預期不到的狀況,就是使用 aa[3] 會破壞 aa[2] 或與 aa[1],因為 String所指到的位址,是記錄字串的開頭,而前面 幾個
Byte 是Header資訊,所以光是宣告 4 x 3 是不夠的,至於Header是什麼,有興趣可以去看該 pdf
而需要做哪些動作,有興趣你可以看反譯組碼

------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2008-10-30 03:00:09, 註解 無‧
aftcast 重新編輯於 2008-10-30 03:07:10, 註解 無‧
aftcast 重新編輯於 2008-10-30 03:10:37, 註解 無‧
aftcast 重新編輯於 2008-10-30 03:14:20, 註解 無‧
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#13 引用回覆 回覆 發表時間:2008-10-30 16:48:42 IP:58.17.xxx.xxx 訂閱
aftcast大大,首先十分感谢您写的二篇文章和热心的帮助!!!

在这篇文章中,有些地方还是不明白,还请您再指点一下,谢谢!
//當getmem(aa,12)時,aa指向一塊12bytes大的地區,但這12byte的內容是亂數的,好比如下
// 00 00 00 00 0c 00 00 00 63 6F 6D 63 ............ (這是16進位的內存dump)
//開始第一個字串放入時,會先準備好 00 00 00 00 (4bytes) 和串字的地址二個參數,然後呼叫ansistring的
//建構式。完成後,那段內存就變成
// 48 3d d5 00 0c 00 00 00 63 6F 6D 63 ............
//上面的48 3d d5 00 是一個位址,指向'abcefg'這個物件! 經排列一下,這個位址是 00d53d48
//但是當第二個字串要放入時,會準備好 0c 00 00 00 和字串的地址,然後呼叫建構式,於是就錯了!
//追入建構子發現它會比對這個初始指針是不是00000000,若不是就出錯。這大概是一種保護機制!!!

当getmem后,第一个字串的位置四个字节会不会有可能不为00 00 00 00 呢?

===================引 用 aftcast 文 章===================
剛忍不住手癢,還是把delphi給裝上去測式…必竟,我個人覺得對memory的管理已經有非常精熟的了解了,怎會有錯呢?!

裝好delphi後,開始測,結果發現了一些compiler的問題,但這些問題容後再談。先把可以run的結果貼出

var
aa:array of string;
begin
getmem(aa,12); //是的,很確定就是我上回的觀念,這是裡12個bytes,你用sizeof(string)也是12,因為就是指針
ZeroMemory(aa,12); //只要多加這一行
//上面那行這是關鍵,在c裡不會有這需要,因為我們慣用pchar這類的型別,而這裡會需要是因為string是一種類
//不是基本的型別,故aa[n]='xxxx'時並不會直接把xxxx的位址直接放入aa[n]裡面。
//事實上compiler是把xxxx的位址和aa[0]的值拿來做string的初始化。
//當getmem(aa,12)時,aa指向一塊12bytes大的地區,但這12byte的內容是亂數的,好比如下
// 00 00 00 00 0c 00 00 00 63 6F 6D 63 ............ (這是16進位的內存dump)
//開始第一個字串放入時,會先準備好 00 00 00 00 (4bytes) 和串字的地址二個參數,然後呼叫ansistring的
//建構式。完成後,那段內存就變成
// 48 3d d5 00 0c 00 00 00 63 6F 6D 63 ............
//上面的48 3d d5 00 是一個位址,指向'abcefg'這個物件! 經排列一下,這個位址是 00d53d48
//但是當第二個字串要放入時,會準備好 0c 00 00 00 和字串的地址,然後呼叫建構式,於是就錯了!
//追入建構子發現它會比對這個初始指針是不是00000000,若不是就出錯。這大概是一種保護機制!!!
編輯記錄
like 重新編輯於 2008-10-30 16:52:14, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#14 引用回覆 回覆 發表時間:2008-10-31 01:13:02 IP:59.115.xxx.xxx 訂閱
我認為有可能不為0!  即使它常常是0 ,但依文件說明來看,不能把巧合當定律。


===================引 用 like 文 章===================


当getmem后,第一个字串的位置四个字节会不会有可能不为00 00 00 00 呢?

------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#15 引用回覆 回覆 發表時間:2008-10-31 22:30:50 IP:218.95.xxx.xxx 訂閱
十分感谢aftcast大大和其他几位大大的帮助!!!
使我对内存管理有了个新的理解!谢谢!
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#16 引用回覆 回覆 發表時間:2008-11-04 03:51:54 IP:122.120.xxx.xxx 訂閱
雖然已結案,但經再深入了解delphi後發現有一個問題,還是po上來以免造成後來見此文的人受害!

原則上上面我講述的內容是正確的,但後來發現那幾個string竟沒有自動的因離開事件而釋放(若整個程式結束,那當然就會釋放)。

原因是delphi處理動態array的方式與c 非常的不同。在delphi裡,若你定義了一個陣列,那compiler會自動的在事件離開前的地方加入DynArrayClear程序。這是穩形的,你在寫程式碼時看不到。

此時,若你正常的使用SetLength來決定array大小,那到了這個程序時,delphi會正確的呼叫ansistring的解構子。但因為我們是自己配置大小,故這個程序沒有發揮正確的做用。而我們也因此只free了3個指標的大小(freemem)。

經反組譯後,有一個發現,delphi裡的array竟和anistring的結構很類似。即在array分配的區塊的前面尚有8bytes記載著參考次數與陣列大小的數據,這與上面講ansistring的情形是一樣的!!!
於是,我將程式碼改成如下:

[code delphi]
var
aa:array of string;
p: ^integer;
begin

p := AllocMem(12 8);
p^ := 1; //參考一次
inc(p); //會跳4byte
p^ := 3; //共有三個指標,即陣列有三個元素
inc(p); //會跳4byte
Pointer(aa) := p; //把我們真正要放指標的位址設定給aa

aa[0]:='a';
aa[1]:='adfs1234';
aa[2]:='adfsa';
ShowMessage(aa[2]);
end;
[/code]

經測式後,那隱藏的DynArrayClear程序會很正確的解構string還有自己,所以也不要再加入freemem(aa); 了

結論: 還是用SetLength來設定動態陣列是最佳的!! 把這個討論當作是一種記憶體管理學習研究來看!

PS。以上的情形再cb (c ) 裡是不會有的。因為c的動態array總是要自己new,自己delete !
------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2008-11-04 04:12:03, 註解 無‧
like
一般會員


發表:30
回覆:35
積分:18
註冊:2005-03-10

發送簡訊給我
#17 引用回覆 回覆 發表時間:2008-11-04 23:32:36 IP:59.55.xxx.xxx 訂閱
aftcast大大,您好!我查看了《Delphi源代码分析》一书,上面讲到“Delphi的动态数组采用跟字符串类同的内存结构。亦即是说,动态数组也具有引用计数位和长度计数位,也类似地采用了引用计数的机制来提高系统的效率”,这和您反编译得出的结论是一样的。
1、书中提到:
正是”引用计数机制”使得这些数据类型的变量必须被初始

[code delphi]
var这
Str:string;
begin
str:='abc';
end;
[/code]
在执行“str:='abc';"时,系统将调用_LStrAsg()来赋值。而_LStrAsg()中,如果"Pointer(Str)<>nil",则需要将Str的引用计数减1,这是, 如果局部变量Str未经初始化就使用,则Pointer(Str)就是一个指向随机地址的指针,这将导致_LStrAsg()中的引用计数操作可能访问非法地址。
=======================================================================
这段话是不是也就体现了您在第9楼发的贴子中所提到的“//追入建構子發現它會比對這個初始指針是不是00000000,若不是就出錯。這大概是一種保護機制!!!”,但这里好像不应该是判断其是不是指针为00000000,而是由于引用计数导致的。我反编译查看了一下,在_LStrAsg()方法中,会有这样一段代码:
mov ecx,[edx-$08]
dec ecx
jl $0e
......
这是不是就是取引用计数后,减1?我查看了[edx-$08]所指的内容,是00000000,其后减1为FFFFFFFF(字符串第一次赋值时,其引用计数都为FFFFFFFF?)。
反编译中在LStrAsg方法中,aa[0]还未赋值,当前值(也是一个地址)所指向的内容前8个字节恰好就为00000000。这真是奇怪。(每次都是这样)
而在aa[1]其值不像是个正常所使用的地址,因此在mov ecx,[edx-$08]处就报错!


2、在程序中getmem(aa,12)后,要用zeromemory(aa,12)进行清零,是不是这就是要对aa进行初始化操作呢?
是不是具有引用计数机制类型的变量,不用在使用完后对其进行释放?

在书中提到“如何结束化”:如果变量,域(记录)或元素(数组)具有引用计数特征,那么结束化过程中,都必将用到相应数据类型中减 少引用计数的例程-例如长字符的内部例程_LStrClr()和_LStrArrayClr()。

这是不是可以这样理解:在前面贴子的代码中:
getmem(aa,12);
zeromemory(aa,12);
....
使用完后,不用对aa进行freemem,因为系统自己会调用结束化例程来释放aa的内存空间?

还请您在百忙中抽空指点,万分感谢!
aftcast
站務副站長


發表:81
回覆:1482
積分:1762
註冊:2002-11-21

發送簡訊給我
#18 引用回覆 回覆 發表時間:2008-11-06 03:52:02 IP:59.115.xxx.xxx 訂閱
關於你提到的
第1點。你的觀察是對的,string這個類在創建時,會在字串的前面8個byte放2項資料,以你下面的'abc'為例,在內存中的樣子應該是
1000 0000 3000 0000 6100 0000 6200 0000 6300 0000 0000 0000
|-------------| |------------| |-------------| |------------| |-------------| |------------|
( 計數) (長度) (a) (b) (c) (字串收尾)
解構時,計算的部份會被減1,然後查看是否等於0 ,若為0表示沒有別的地方有再參考到,compiler會安全的呼叫freemem來釋放6*4=24bytes的內存。若減完後不等於0,那就等跳錯給你看了!

此外你說「如果"Pointer(Str)<>nil",则需要将Str的引用计数减1」,是的! 這是一種保護機制! 而減1這個程序是表示因為已查到有誤(因為str <> nil),即將要進入解構剛創的string。

你說「如果局部变量Str未经初始化就使用,则Pointer(Str)就是一个指向随机地址的指针,这将导致_LStrAsg()中的引用计数操作可能访问非法地址。」這是對的! 但為何要這樣做呢? 是因為string建構子根本不知道這個不是nil的pointer(str)它是目前有指向有用的資料,或是因為你剛getmem後造成的亂數非nil的pointr。因為無法辦識,所以它就一律的把非nil的情形當做是錯的!(其實只是"有疑慮的"!)。所以,為了讓它不會搞不清楚。程序人員正常都需要把get後的亂數資料調整成0(nil)。(但是,事實上我們心中很清楚,這個nil pointer並非真的有指向任何有用的資料,它只是一個亂數,它可以被賦與(覆蓋)值。比如說原來是 0c00 0000 (亂數) 然後賦與成 125d 4300(剛創好的string位址),這是沒問題的! 但建構子不了解oc00 0000 是你剛getmem來的嗎? 還是指向一個正常的資料? 建構子預期你給它 0000 0000 ,它就一切安心的可以賦與 125d 4300。

你說「zeromemory(aa,12)进行清零,是不是这就是要对aa进行初始化操作呢」,是的,因為要符合稍後string建構子的預期,然後安心賦值。


「使用完后,不用对aa进行freemem,因为系统自己会调用结束化例程来释放aa的内存空间?」 這點要配合我上一篇講的,要正確的初始aa這個動態array才行。何謂正確?就是指除在aa的前面加入8byte… 詳請請用心再看上面的最後一篇所述!! 若你依舊使用12byte,而非12 8byte,那麼那三條string所占的空間是不會被釋放的,僅僅aa本身的12byte會被釋放。這點你要用心思考再思考!



------



蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2008-11-06 03:54:06, 註解 無‧
aftcast 重新編輯於 2008-11-06 03:54:59, 註解 無‧
aftcast 重新編輯於 2008-11-06 03:56:10, 註解 無‧
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#19 引用回覆 回覆 發表時間:2008-11-07 08:36:37 IP:59.125.xxx.xxx 訂閱
呵呵 aftcast 的精神可嘉

所以才會說直接 alloc (12) 不當,因為若你認為你建立的是 pointer of pointer of pointer 但 compiler 認為是pointer of pointer ,則錯誤,反過來,也是,唯有兩者一樣,才會無誤,正確的說,compiler 已經是固定的了,唯有你的想法跟 compiler 一樣,才會正確,不然一個 pointer 到底要剝幾層,才會是真實數值,只有你與天,曉得

因此,
雖然可以自己分配,但你需要做哪些動作,你知道嗎?
如果你不知道,請乖乖用 Setlength,就算你知道,也請用 SetLength (你又不是要寫 Compile,別沒事找事做)

PChar 就用 PChar 的作法,String 就用 String 的方式,一如compiler 老是在抱怨 unsigned int does not good with signed int,而我們總是 type case it,最後出現,不可預期又找不到的 BUG 一般,石頭總是我們自己搬來砸的,即使砸的是我們的腳

除非你有 aftcast 的精神與程式一定出錯的心理建設,不然,一定又是幹聲一堆囉~

再次感謝 aftcast 的努力,將 delphi 的記憶體結構說明的清楚
有興趣知道其他的,提供給大家參考一下,還可以去看deplhi 所附的 pascal 語言pdf 與 Inside VCL
roviury
一般會員


發表:3
回覆:49
積分:15
註冊:2008-08-28

發送簡訊給我
#20 引用回覆 回覆 發表時間:2009-01-12 13:46:39 IP:203.186.xxx.xxx 訂閱
對於string,
用tstringlist更好
因為string本身是一個不定長度的變數
管理又麻煩
使用array of string,不如使用內置的tstringlist
var
aa:tstringlist;
begin
aa:=tstringlist.create;
aa.add('abcefg');
aa.add('adfs');
aa.add('adfsa');
freeandnil(aa); //aa.free aa:=nil
end;

使用要使用array of string,也讓系統自行分配吧
var
aa:array of string;
begin
setlength(aa,3);
aa[0]:='abcefg';
aa[1]:='adfs';
aa[2]:='adfsa'; //为何这里会报错,访问内存地址错误
finalize(aa);
end;



印象中,sizeof(string)是4byte,好像是一個point,只是記錄第一個char的位置
用getmem真正使用起來,是難以分配到正確的記憶體的
(
至少我的delphi不能
var
aa:array of string;
begin
getmem(aa,3);
end;
)
編輯記錄
roviury 重新編輯於 2009-01-12 13:55:03, 註解 無‧
系統時間:2017-10-24 1:09:39
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!