Object Pascal中String型別的內幕探討 (轉)
在 Pascal中,String(準確的說是AnsiString)是一種可變長度的字串,透過PChar(AString)可以將其轉換為與 相相容的字元指標型別。事實上,String型別就是一個指標,你可以用Sizeof去讀取它的大小,不論字串的實際長度是多少,Sizeof(AString)永遠是4。String與一般的Null-Tenated字元指標不同的是,String還要保留另外的一部分空間,用於記錄字串長度和引用計數等資訊。String型別在中的確切格式如下::namespace prefix = o ns = "urn:schemas--com::office" />
(4位元組)分配大小+(4位元組)引用計數+(4位元組)字串長度+(不定長)字元陣列+(1位元組)$0結束字元
為了驗證這一點,我們可以在中新增一個作用域為private的String變數,在程式中動態改變它的長度和內容,同時觀察它的分配大小和長度發生了什麼變化。另外,為了觀察引用計數的變化,只有在兩個字串互相複製的時候才能體現出來,我們在程式中也要實現這一點。
請新建一個Application,在窗體上放置一個Edit,一個ListBox和三個Button。其中,Edit用來改變字串的內容;ListBox用來記錄跟蹤資訊;三個按鈕分別用於觀察字串的當前狀況,觀察字串的引用計數變化情況和清空列表內容。
在Form的宣告中新增一個變數:
type
TForm1=class(TForm)
...
private
s : string;
end;
新增三個按鈕的事件處理如下:
procedure TForm1.Button1Click(Sender: TObject);
var
psz : PChar;
pdw : PD;
dw1, dw2, dw3 : DWord;
begin
s := Edit1.Text;
psz := PChar(s);
pdw := PDWORD(psz);
Dec(pdw);Dec(pdw);Dec(pdw);
dw1 := pdw^;
Inc(pdw);dw2 := pdw^;
Inc(pdw);dw3 := pdw^;
ListBox1.Items.Add( Format('[Current]Size:%d, Ref:%d, Len:%d',
[dw1,dw2,dw3]) );
end;
procedure TForm1.Button2Click(Sender: TObject);
var
psz : PChar;
pdw : PDWORD;
dw1, dw2, dw3 : DWord;
s2 : string;
p1, p2 : Pointer;
begin
s := Edit1.Text;
psz := PChar(s);
pdw := PDWORD(psz);
Dec(pdw);Dec(pdw);Dec(pdw);
dw1 := pdw^;
Inc(pdw);dw2 := pdw^;
Inc(pdw);dw3 := pdw^;
ListBox1.Items.Add( Format('[Before assign]Size:%d, Ref:%d, Len:%d',
[dw1,dw2,dw3]) );
s2 := s;
p1 := Pointer(PChar(s));
p2 := Pointer(PChar(s2));
ShowMessage(Format('p1=%p,p2=%p',[p1,p2]));
psz := pChar(s);
pdw := PDWORD(psz);
Dec(pdw);Dec(pdw);Dec(pdw);
dw1 := pdw^;
Inc(pdw); dw2 := pdw^;
Inc(pdw); dw3 := pdw^;
ListBox1.Items.Add( Format('[After assign]Size:%d, Ref:%d, Len:%d',
[dw1,dw2,dw3]) );
s2 := s2 + 'Another string';
p1 := Pointer(PChar(s));
p2 := Pointer(PChar(s2));
ShowMessage(Format('p1=%p,p2=%p',[p1,p2]));
psz := pChar(s);
pdw := PDWORD(psz);
Dec(pdw);Dec(pdw);Dec(pdw);
dw1 := pdw^;
Inc(pdw); dw2 := pdw^;
Inc(pdw); dw3 := pdw^;
ListBox1.Items.Add( Format('[After COW]Size:%d, Ref:%d, Len:%d',
[dw1,dw2,dw3]) );
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
ListBox1.Items.Clear;
end;
如果你對指標的概念比較清楚的話,上面的程式碼是不難理解的。下面是該程式的輸出結果:
[Current]Size:22, Ref:5, Len:5
[Before Assign]Size:22, Ref:2, Len:5
[After Assign]Size:22, Ref:3, Len:5
[After COW]Size:22, Ref:2, Len:5
觀察上述結果,可以得出幾個結論:
1.“分配大小”和“字串長度”之間存在著一種固定的數量關係,即分配大小=字串長度+17。為什麼會有這種關係?請你再看一看String型別的記憶體分佈:(4位元組)分配大小+(4位元組)引用計數+(4位元組)字串長度+(不定長)字元陣列+(1位元組)$0結束字元,4+4+4+(strlen)+1,應該是13+(strlen)才對,也就是說應該還有4位元組的空間,其用途尚不清楚。值得一提的是,如果你將字串清空,那麼Len的結果可能不是你所想象的0,而是一個讓你大吃一驚的數字。
2.因為分配大小和字串長度都是用4位元組來表示的,而且String型別是動態分配記憶體,所以字串最大可能的長度應該是2^32-17個位元組。
3.在複製字串的時候,Object Pascal並不是把字串簡單的複製一份,而是採取了引用計數的方法,將兩個字串指向同一個記憶體空間,同時引用計數加1。當字串變數被清除的時候,引用計數減1,如果引用計數已經減為0,表明該字串可以真正被清除了。顯然,這種方法比複製整個字串的要高。
4.在給字串賦值的時候,Object Pascal首先會檢查字串的引用計數是否為1。如果是,按照一般的方法直接賦值即可;否則,就說明有兩個以上字串指向同一個地址,這種情況就複雜多了。Object Pascal使用的是Copy-on-Write機制(COW),為當前字串另外開闢一個緩衝區,將新內容拷入;同時,原來的字串引用計數要減一。
5.知道了String的記憶體佈局,我們也就知道了PChar(str)的意義了。不過,使用PChar的同時也就丟失了String的動態增長和引用計數的功能,所以一定要小心,另外要注意PChar長度的計算和字串長度一定要同步,否則會出問題。比如,下面的程式碼就不能正常工作:
var
str : string;
begin
SetLength(str,256);
GetWindowDirectory(PChar(str),256);
str := str + ‘win.ini’;
end;
這樣的結果是不正確的。之所以不正確,是因為SetLength將字串長度設成了256,而PChar計算的長度只到第一個$0為止。正確的方法應該是:
SetLength(str,256);
GetWindowsDirectory(PChar(str),256);
SetLength(str,StrLen(PChar(str)));
str := setr + ‘win.ini’;
說明:上面的程式是在 5下測試透過的。Borland並不保證String的記憶體結構在以後的Delphi版本中會保持不變,所以,上述例子只是作為測試用,實際的程式中不應該這樣使用String,謹此說明。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1003048/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C++/Object Pascal中迴圈結構的一點差異及討論 (轉)C++Object
- Object Pascal Style Guide (轉)ObjectGUIIDE
- 全面探討PL/SQL的複合資料型別(轉)SQL資料型別
- JAVA中Object轉StringJavaObject
- 簡單探討TypeScript 列舉型別TypeScript型別
- 日期型別與String型別的轉換型別
- Object Pascal:從物件指標談起 (轉)Object物件指標
- JavaScript中的Object的引用型別JavaScriptObject型別
- TypeScript 中令人迷惑的物件型別:Object、{} 和 objectTypeScript物件型別Object
- C#中的介面和泛型集合探討C#泛型
- 淺談Object Pascal的指標Object指標
- BigDecimal轉為String型別、int型別Decimal型別
- 在ASP中讀取ORACLE中的BLOB型別的欄位的值,不用Oracle Object for Object (轉)Oracle型別Object
- Redis 字串型別實現內幕Redis字串型別
- Java中String和byte型別互相轉換Java型別
- 關於多型實現Singleton模式的探討 (轉)多型模式
- 國內應用軟體開發管理的探討 (轉)
- 深入探討遊戲AI型別及其設計要點遊戲AI型別
- String.valueOf和強制型別轉換(String)的區別型別
- Delphi的程式語言Object Pascal(1)Object
- 型別轉換(int 和 String)型別
- java中判斷Object物件型別JavaObject物件型別
- JavaScript引用型別-Object型別JavaScript型別Object
- 有容雲——窺探Docker中的Volume Plugin內幕DockerPlugin
- 關於.Net中屬性的使用探討(一) (轉)
- 關於.Net中屬性的使用探討(二) (轉)
- Map和String型別之間的轉換型別
- IsPostBack深入探討(轉轉轉轉轉)
- String 型別型別
- JS中的資料型別轉換:String轉換成Number的3種方法JS資料型別
- c++中幾種常見的型別轉換。int與string的轉換,float與string的轉換以及string和long型別之間的相互轉換。to_string函式的實現和應用。C++型別函式
- 全面解析Java中的String資料型別Java資料型別
- 【轉】關於oracle中Move機制的一點探討Oracle
- 淺談Java String內幕(1)Java
- 探討後端選型中不同語言及對應的Web框架後端Web框架
- jsp頁面number型別自動轉為String型別JS型別
- pandas中字串object 轉化 datetime 型別字串Object型別
- String和基本資料型別的相互轉換資料型別