Delphi 的 string (AnsiString/UnicodeString) 採用 **引用計數 + Copy-on-Write (COW)** 機制,內存佈局包含:長度前綴、字元資料、#0 終結符、引用計數。理解這套機制能解釋字串賦值、修改、參數傳遞的行為差異,並指導高效能編程。
[引用計數: 4 Bytes] [長度: 4 Bytes] [字元資料...] [#0: 1 Byte]
↑ ↑ ↑
str-8 str-4 str (指針指向這裡)
var
str: string;
pstr: PString; // ^string
pc: PChar;
begin
// 未賦值前:棧上指標存在,堆上內存為 nil
ShowMessage(IntToStr(Integer(@str))); // 棧上指標地址
ShowMessage(IntToStr(Integer(str))); // 0 (nil)
str := 'Delphi';
// 賦值後:棧上指標不變,堆上分配新內存
ShowMessage(IntToStr(Integer(@str))); // 同前
ShowMessage(IntToStr(Integer(str))); // 堆上實際地址 (如 $4580800)
// 通過指標地址讀取
pstr := @str;
ShowMessage(pstr^); // 'Delphi'
// 通過實際地址讀取
pc := PChar(Integer(str));
ShowMessage(pc); // 'Delphi'
end;
var
str, s1, s2: string;
pint: PInteger;
begin
str := Self.Caption; // 假設 'Form1' (長度 5)
s1 := str; // 引用計數 +1
s2 := str; // 引用計數 +1
// 三個變數指向同一塊堆內存
ShowMessage(IntToStr(Integer(str))); // 同一地址
ShowMessage(IntToStr(Integer(s1))); // 同一地址
ShowMessage(IntToStr(Integer(s2))); // 同一地址
// 讀取長度 (前移 4 Bytes)
pint := PInteger(Integer(str) - 4);
ShowMessage(IntToStr(pint^)); // 5
// 讀取引用計數 (前移 8 Bytes)
pint := PInteger(Integer(str) - 8);
ShowMessage(IntToStr(pint^)); // 3
// 修改 s1 觸發 COW:複製內存、原塊引用計數 -1、新塊引用計數 = 1
s1 := 'NewForm'; // 此時 str/s2 仍指向 'Form1',s1 指向新內存 'NewForm'
end;
const
CSTR = 'Constant';
var
LocalStr: string;
begin
LocalStr := 'Local';
// 測試發現:常量、非全域區域變數引用計數常為 -1
// 表示「不參與引用計數管理,永不釋放」或「編譯器優化標記」
end;
| 操作 | 原內存塊引用計數 | 新內存塊引用計數 | 說明 |
|---|---|---|---|
| 聲明變數 | - | - | 棧上指標 = nil |
| 賦值字面值/運算式 | - | 1 | 分配新塊 |
| 變數賦值 (s1 := s2) | +1 | - | 共享同一塊 |
| 修改字串 (s1 := 'x') | -1 | 1 | COW 複製新塊 |
| 修改字元 (s1[i] := 'x') | -1 | 1 | COW 複製新塊 (Delphi 2009+) |
| 離開作用域 / Free | -1 | - | 歸零則釋放內存 |