管理內存
D不顯式管理內存.本章爲垃集
,可以研究std.allocator
及各種管理內存方法.
取相鄰
變量地址
import std.stdio;
void main() {
int i;
int j;
writeln("i: ", &i);
writeln("j: ", &j);
}
D的動態變量放在垃集
內存塊上.當不用變量了,垃集根據適當算法終止它
算法大致爲:掃描所有指針/引用直接/間接可達的內存塊.可達標記爲在用,其餘未用.終止不可訪問內存塊的對象和結構,他們可供未來使用.定義根爲每個線程的所有程序棧,通過GC.addRoot或GC.addRange
添加所有全局,線程本地變量,和額外數據.
一些垃集算法可以聚攏內存,爲保持正確,所有指向這些對象的指針和引用都得更新,D不這樣做.
如果精確知道哪塊內存包含指針,哪塊不包含,則叫精確垃集
.如果按指針掃描所有指針,則叫保守垃集
.D的垃集是部分保守的,只掃描含指針塊,但會掃描這些塊的所有數據.因此,有時都沒收集有些塊,而泄露
內存.大塊易容易出錯,有時建議人工釋放未用大塊,以避免出錯.
未指定終結器的執行順序.有時引用成員的對象可能釋放在包含這個成員的對象前釋放.即本對象含有已釋放對象成員的引用
.因而,在析構器中,不要訪問指向動態變量的類成員引用.這與c++確定性的內存析構順序
不一樣.
可能有各種原因啓動垃集.如需要更多空間.依賴於垃集的實現,在垃集循環中分配新對象,會衝突垃集進程自身.在收集循環過程中,所有線程都得停止.因爲你地址
變了.就像一切停止了.
多數時候,程序員不需要干預垃集
,但可用在core.memory
中定義的函數延遲和分發
垃集循環.
啓動和延遲垃集週期
GC.disable();//禁止垃集週期
//性能重要區
GC.enable();//允許垃集
然而GC.disable()
如果垃集要獲取更多內存時,不保證執行時避免不垃集.它仍然要取內存並跑垃集.
除了自動,你還可顯式調用垃集循環:GC.collect()
import core.memory;
// ...
GC.collect();//收集
垃集一般不返回內存塊給操作系統,以供未來使用.但可用GC.minimize();
退回一部分.
分配內存.
ubyte[100] buffer;
,固定數組緩衝.
還可以爲void
型.void[100] buffer = void;
void型
無法分配.init
,所以必須賦初值void
.
我們僅使用core.memory(還有其他各種有用特徵)中的GC.calloc
來保留內存.
也可用在std.c.stdlib
中的分配內存函數.
import core.memory;
// ...
void * buffer = GC.calloc(100);//0值填充內存
//並返回首地址.
可以int * intBuffer = cast(int * )buffer;
但一般:
int * intBuffer = cast(int*)GC.calloc(100);
一般這樣:
int * intBuffer = cast(int*)GC.calloc(int.sizeof * 25);
類的區別:類變量與類對象的大小不一樣.
.sizeof
是類變量大小,一般==size_t
,64位則爲8,32位則爲4.類對象實際大小這樣取:_ _traits(classInstanceSize)
MyClass * buffer = cast(MyClass*)GC.calloc(__traits(classInstanceSize, MyClass) * 10);//10個
void * buffer = GC.calloc(10_000_000_000);
空間不夠時,拋core.exception.OutOfMemoryError
異常.
返回用GC.free(buffer);
//釋放.
要顯式消滅.對每個變量顯式調用destroy()
函數.
在垃集收集與釋放過程中對類/構
採取不同內部機制來調用終止器.
最好的方法是用new
來確保調用析構,這樣的話GC.free
會調用析構器.
void * oldBuffer = GC.calloc(100);
// ...
void * newBuffer = GC.realloc(oldBuffer, 200);
程序覺得先前內存不夠了,就再分配
,返回一個新地址.
1,如果舊區域後面能夠分配足夠內存.則原位擴展.
2,舊區域後面已用或內存不夠,則分配到新的更大的區域,並把源數據複製過去.
3,可給舊區域參數賦值爲null
,此時簡單分配新內存.
4,比舊區域小,舊區域的剩餘部分返回垃集.
5,大小置爲0,此時簡單釋放內存.
GC.realloc
改編自C的
重分配.太複雜.接口不好.
GC.realloc
令人驚訝的一點:即使用GC.calloc
分配了原內存,也不清理擴展部分.因而當爲0初化內存時,reallocCleared
很有用.
import core.memory;
/*像GC.realloc一樣工作,擴展內存則清除額外字節.*/
void * reallocCleared(
void * buffer,
size_t oldLength,
size_t newLength,
GC.BlkAttr blockAttributes = GC.BlkAttr.NONE,
const TypeInfo typeInfo = null) {
/*將實際工作分派給GC.realloc.*/
buffer = GC.realloc(buffer, newLength,blockAttributes, typeInfo);
//擴展,則清理額外字節
if (newLength > oldLength) {
import std.c.string;
auto extendedPart = buffer + oldLength;
auto extendedLength=newLength - oldLength;
memset(extendedPart, 0, extendedLength);
//清理,從擴展部分,到長度爲0的賦值
}
return buffer;
}
用std.c.string
中的memset
來清理新分配內存
memset
用一個指針和長度來指定給定內存的值
GC.extend
也類似,它僅應用上面的第1項,如果內存不能原位顯式擴展,什麼都不做直接返回0,
內存塊屬性
GC.calloc
和其他分配函數的可選屬性BlkAttr
,
NONE
,無屬性.
FINALIZE
,應終止內存塊中對象.
垃集假定由程序員控制顯式分配內存的對象的生命期,垃集並不終止這些內存區的對象.GC.BlkAttr.FINALIZE
用於請求垃集執行對象的析構器.
Class * buffer = cast(Class*)GC.calloc( __traits(classInstanceSize, Class) * 10, GC.BlkAttr.FINALIZE);
注意.FINALIZE
依賴塊上正確的實現細節.強烈建議垃集
注意new
分配的細節.
NO_SCAN
,指定垃集不掃描這片內存區.
一個區的字節值像指向其他塊不相關對象的指針.這時,即使他們實際生命期已結束,垃集假定他們還在用.
標記一個不含任何對象指針的內存塊爲GC.BlkAttr.NO_SCAN
,
int * intBuffer =cast(int * )GC.calloc(100, GC.BlkAttr.NO_SCAN);
內存塊的整
值可爲任意值,而不擔心認錯爲指針.
NO_MOVE
,不應移動內存塊
中對象.
APPENDABLE
,D運行時內部標誌,加速快速附加,分配內存時,你最好別用.
NO_INTERIOR
,指定僅存在塊首地址的指針.這允許減少錯誤指針,因爲當跟蹤指針時不計數中間塊指針.
多個標誌用|
:
const attributes =GC.BlkAttr.NO_SCAN | GC.BlkAttr.NO_INTERIOR;
一般,垃集只知道自己函數保留的內存塊,並僅掃描他們.
它不知道std.c.stdlib.calloc
分配的內存塊.
GC.addRange
用於引入不相關內存塊,在用std.c.stdlib.free
釋放他們前用GC.removeRange
.
有時,沒有到垃集內存塊的引用,如僅有的引用在c庫中,垃集不知道引用,並假定該內存塊不再用了.
GC.addRoot
按根引入內存塊,收集循環時將掃描他們.
通過那個內存塊直接/間接可達的變量,都標記爲是活的.
當不在用內存塊時調用GC.removeRoot
.
擴展存儲區示例
struct Array(T) {
T * buffer; // 內存區
size_t capacity; // 容量
size_t length; // 長度
T element(size_t index) {//返回指定元素
import std.string;
enforce(index < length,format("Invalid index %s", index));
return *(buffer + index);
}
void append(T element) {//追加至尾
writefln("附加元素%s", length);
if (length == capacity) {
//沒有新元素空間,趕緊加
size_t newCapacity = capacity + (capacity / 2) + 1;
increaseCapacity(newCapacity);
}
*(buffer + length) = element;++length;
//在尾放元素.
}
void increaseCapacity(size_t newCapacity) {
writefln("從%s到%s增加元素",capacity, newCapacity);
size_t oldBufferSize = capacity * T.sizeof;
size_t newBufferSize = newCapacity * T.sizeof;
buffer = cast(T*)reallocCleared(buffer, oldBufferSize, newBufferSize,GC.BlkAttr.NO_SCAN);
//該內存塊不掃描指針
capacity = newCapacity;
}
}
簡單數組
雙精類型
import std.stdio;
import core.memory;
import std.exception;
// ...
void main() {
auto array = Array!double();
const count = 10;
foreach (i; 0 .. count) {
double elementValue = i * 1.1;
array.append(elementValue);
}
writeln("The elements:");
foreach (i; 0 .. count) {
write(array.element(i), ' ');
}
writeln();
}
對齊,整
對齊爲4,未對齊的內存地址,更慢,導致線程錯誤,某些類型僅在對齊地址上工作.
.alignof
爲默認對齊值.對類,是類變量,不是變對象(實體),類對象的對齊用std.traits.classInstanceAlignment
.
import std.stdio;
import std.meta;
import std.traits;
struct EmptyStruct {
}
struct Struct {
char c;
double d;
}
class EmptyClass {
}
class Class {
char c;
}
void main() {
alias Types = AliasSeq!(char, short, int, long,
double, real,
string, int[int], int*,
EmptyStruct, Struct,
EmptyClass, Class);
writeln(" Size Alignment Type\n",
"=========================");
foreach (Type; Types) {
static if (is (Type == class)) {
size_t size = __traits(classInstanceSize, Type);
size_t alignment = classInstanceAlignment!Type;
} else {
size_t size = Type.sizeof;
size_t alignment = Type.alignof;
}
writefln("%4s%8s %s",
size, alignment, Type.stringof);
}
}
打印不同類型的對齊方式.不同環境可能不同:
Size Alignment Type
=========================
1 1 char
2 2 short
4 4 int
8 8 long
8 8 double
16 16 real
16 8 string
8 8 int[int]
8 8 int*
1 1 EmptyStruct
16 8 Struct
16 8 EmptyClass
17 8 Class
爲了正確與效率
,必須在匹配他們對齊的地址上構建對象.
(candidateAddress + alignmentValue - 1) / alignmentValue * alignmentValue
//假定爲整,且都截斷了.
可放對象的最近地址值,
T * nextAlignedAddress(T)(T * candidateAddr) {
import std.traits;
static if (is (T == class)) {
const alignment = classInstanceAlignment!T;
} else {
const alignment = T.alignof;
}
const result = (cast(size_t)candidateAddr + alignment - 1)
/ alignment * alignment;
return cast(T*)result;
}
從模板參數中推導出類型,由於不可能是void *
,必須顯式提供void *
的重載.
void * nextAlignedAddress(T)(void * candidateAddr) {
return nextAlignedAddress(cast(T*)candidateAddr);
}
//簡單轉發至上面代碼
當用emplace
原位構造時,有用.
size_t sizeWithPadding(T)() {
static if (is (T == class)) {
const candidateAddr = __traits(classInstanceSize, T);
} else {
const candidateAddr = T.sizeof;
}
return cast(size_t)nextAlignedAddress(cast(T*)candidateAddr);
}
統計包含間隙(padding)
的對象的大小
.offsetof
屬性
struct A {
byte b; // 1字節
int i; // 4字節
ubyte u; // 1字節
}
static assert(A.sizeof == 12);
有間隙.
.offsetof
.對象從頭到本變量的距離
打印類型佈局,用.offsetof
決定間隙類型.
void printObjectLayout(T)()
if (is (T == struct) || is (T == union)) {
import std.stdio;
import std.string;
writefln("=== Memory layout of '%s'" ~
" (.sizeof: %s, .alignof: %s) ===",
T.stringof, T.sizeof, T.alignof);
void printLine(size_t offset, string info) {
//打印單行佈局信息
writefln("%4s: %s", offset, info);
}
//已觀察內邊距,打印其信息
void maybePrintPaddingInfo(size_t expectedOffset,
size_t actualOffset) {
if (expectedOffset < actualOffset) {
//有間隙.不一樣
const paddingSize = actualOffset - expectedOffset;
printLine(expectedOffset, format("... %s-byte PADDING", paddingSize));
}
}
//如下個成員無間隙的預期偏移
size_t noPaddingOffset = 0;
//`__traits(allMembers)`是類型成員名`串`
foreach(memberName; __traits(allMembers, T)) {
mixin (format("alias member = %s.%s;",
T.stringof, memberName));
const offset = member.offsetof;
maybePrintPaddingInfo(noPaddingOffset, offset);
const typeName = typeof(member).stringof;
printLine(offset,
format("%s %s", typeName, memberName));
noPaddingOffset = offset + member.sizeof;
}
maybePrintPaddingInfo(noPaddingOffset, T.sizeof);
}
使用
struct A {
byte b;
int i;
ubyte u;
}
void main() {
printObjectLayout!A();
}
一個最小化的技術是從大到小排序成員.
struct B {//上移
int i;byte b;ubyte u;
}
void main() {
printObjectLayout!B();
}
align
屬性.用於指定變量,用戶定義類型,及其成員的
對齊.
align (2)//'S'對象對齊,2字節對齊邊界
struct S {
byte b;
align (1) int i; //成員i的對齊,按1字節對齊
ubyte u;
}//1字節,則無間隙.
//0,1,5-6.剛好6字節大小,無間隙了
void main() {
printObjectLayout!S();
}
雖然對齊
可減小大小,但不能滿足默認對齊時,性能損失很大.一些cpu上,用未對齊數據,可能會崩潰.
可直接對變量指定對齊如align (32) double d;
.
但new
分配對象必須爲size_t
的整數倍.這是垃集
要求的.否則未定義行爲.
在特定內存位置
構造變量.
新
要完成:
1,內存足夠大,新內存區爲原始的,不與任何系統/對象關聯.
2,在內存位置調用對象構造器,此後,對象被放在內存上.
3,配置內存塊,使有必要標誌和基礎設施來正確的釋放對象
第1可用類似GC.calloc
顯式完成,第2也可以.
可以在指定位置用std.conv.emplace
構造變量.
import std.conv;
// ...
emplace(address,...);//位置,構造參數...
未明確指定構/類
類型,因爲emplace
可從指針位置推導出來類型.
Student * objectAddr = nextAlignedAddress(candidateAddr);
// ...
emplace(objectAddr, name, id);
根據指針類型推導出對象類型
import std.stdio;
import std.string;
import core.memory;
import std.conv;
// ...
struct Student {
string name;
int id;
string toString() {
return format("%s(%s)", name, id);
}
}
void main() {
/* 此類型的一些信息. */
writefln("Student.sizeof: %#x (%s) bytes",
Student.sizeof, Student.sizeof);
writefln("Student.alignof: %#x (%s) bytes",
Student.alignof, Student.alignof);
string[] names = [ "Amy", "Tim", "Joe" ];
auto totalSize = sizeWithPadding!Student() * names.length;
/* 爲所有學生對象保留空間
*警告!還未構造通過此切片可訪問的對象,正確構造前,不要訪問他們*/
Student[] students =
(cast(Student*)GC.calloc(totalSize))[0 .. names.length];
foreach (int i, name; names) {
Student * candidateAddr = students.ptr + i;
Student * objectAddr =
nextAlignedAddress(candidateAddr);
writefln("address of object %s: %s", i, objectAddr);
const id = 100 + i;
emplace(objectAddr, name, id);
}
writeln(students);//都構造好了,可用了.
}
類變量不一定是類實體的精確類型.如動物
類變量,可引用貓
對象.因而原位
不能從指針
決定對象類型
.
因而必須顯式指定類型.注意,類指針是類變量的指針,而不是類對象的指針.因而,指定實際類型允許程序員原位類對象/類變量.
必須用以下語法按void[]切片
指定類對象的內存位置.
Type variable=emplace!Type(voidSlice,構造參數...)
按
void[]切片原位
在切片上構造類對象,並返回它的類變量(引用).
interface Animal {
string sing();
}
class Cat : Animal {
string sing() {
return "meow";
}
}
class Parrot : Animal {
string[] lyrics;
this(string[] lyrics) {
this.lyrics = lyrics;
}
string sing() {
//std.algorithm.joiner用指定分隔符合並區間元素
return lyrics.joiner(", ").to!string;
}
}
在動物
層次上原位
對象.動物層次
對象挨個放在GC.calloc
分配的內存塊,子類大小不同,展示後面對象位置可由先前大小決定.
這樣分配緩衝:
auto capacity = 10_000;
void * buffer = GC.calloc(capacity);//應該合適
確保對對象,有可用容量.
Cat cat = emplace!Cat(catPlace);
// ...
Parrot parrot =emplace!Parrot(parrotPlace, [ "squawk", "arrgh" ]);
注意Parrot
的構造參數在對象地址後指定.
emplace
返回的變量在存儲在稍後每一循環使用的動物
切片裏面.
Animal[] animals;
// ...
animals ~= cat;
// ...
animals ~= parrot;
foreach (animal; animals) {
writeln(animal.sing());
}
全部:
import std.stdio;
import std.algorithm;
import std.conv;
import core.memory;
// ...
void main() {
Animal[] animals;//動物變量切片
auto capacity = 10_000;//硬編碼分配緩衝,假定合適,一般必須驗證
void * buffer = GC.calloc(capacity);
void * catCandidateAddr = buffer;
void * catAddr = nextAlignedAddress!Cat(catCandidateAddr);
//先放一個貓
writeln("Cat address : ", catAddr);
size_t catSize = __traits(classInstanceSize, Cat);
//對類對象原位,要求一個`空[]`切片,我們必須先從指針產生切片
void[] catPlace = catAddr[0..catSize];
Cat cat = emplace!Cat(catPlace);
//切片裏面構造貓,爲稍後用存儲返回類變量
animals ~= cat;
void * parrotCandidateAddr = catAddr + catSize;
//在滿足對齊要求的下個可用地址裏面構造鸚鵡
void * parrotAddr =nextAlignedAddress!Parrot(parrotCandidateAddr);
writeln("Parrot address: ", parrotAddr);
size_t parrotSize = __traits(classInstanceSize, Parrot);
void[] parrotPlace = parrotAddr[0..parrotSize];
Parrot parrot =emplace!Parrot(parrotPlace, [ "squawk", "arrgh" ]);
animals ~= parrot;
foreach (animal; animals) {//用對象
writeln(animal.sing());
}
}
函數模板newObject(T)
比對每個對象重複構造更有用.
顯式消滅對象
new
的逆向操作是消滅對象並把內存返回給垃集.一般在未指定時間自動運行.有時需要在指定點析構.當結束對象時,立即執行析構器,而在析構器中可能得關閉文件
對象.
destroy(variable);
調用對象的析構器.
調用析構器後,destroy
置變量爲.init
狀態,注意類變量的.init
狀態爲無效(null)
,所以一旦消滅類變量後,就不能用了.destroy
只是執行析構器,由垃集
決定重用對象佔用的內存.
警告:當同構指針
用時,destroy
必須接受被指對象,而不是指針
.否則,指針爲無效
,而未消滅(析構)對象.
import std.stdio;
struct S {
int i;
this(int i) {
this.i = i;
writefln("用%s值構造", i);
}
~this() {
writefln("用%s值析構", i);
}
}
void main() {
auto p = new S(42);
writeln("destroy()前");
destroy(p);//錯誤用法
writeln("destroy()後");
writefln("p: %s", p);
writeln("離開主");
}
當destroy
接收指針時,指針->無效
.而不是指針的對象
變爲無效.
正確寫法是destroy( * p);
最後一行是爲相同對象再執行一次析構器,現在其值爲S.init,這就是垃集
浪費的地方.
按名在運行時構造對象
Object
的factory
成員函數取類類型的全名
作參數,構造類型對象,並返回那個對象的類變量.
module test_module;
import std.stdio;
interface Animal {
string sing();
}
class Cat : Animal {
string sing() {
return "meow";
}
}
class Dog : Animal {
string sing() {
return "woof";
}
}
void main() {
string[]toConstruct = [ "Cat", "Dog", "Cat" ];
Animal[] animals;
foreach (typeName; toConstruct) {
// __MODULE__僞變量是當前模塊名,可在編譯時作爲串字面量.
const fullName=__MODULE__~'.' ~ typeName;
writefln("構造 %s", fullName);
animals ~= cast(Animal)Object.factory(fullName);
}
foreach (animal; animals) {
writeln(animal.sing());
}
}
雖然無顯式new
式,但構造了3個對象,並加至動物切片.
注意Object.factory()
以全名
作參數,factory
返回類型爲對象
,在程序使用前,必須轉換
爲實際類型.
垃集
在未指定時間掃描.確定程序不再使用對象,銷燬並回收他們.
程序員可用GC.collect, GC.disable, GC.enable, GC.minimize,
一定程度上控制他們
GC.calloc
和其他函數保留內存,GC.realloc
擴展先前內存,GC.free
將其返回至垃集.
可給分配的內存標記爲GC.BlkAttr.NO_SCAN, GC.BlkAttr.NO_INTERIOR
等.
.alignof
是類型的默認對齊,可用classInstanceAlignment
取類對象的默認對齊.
.offsetof
是對象成員與對象開始的偏移字節數.
align
指定變量,用戶定義類型,成員
的對齊.
emplace
構造結構時用指針,類對象時用void[]
切片.
destroy
執行對象析構器,你必須消滅構對象
,而不是其指針.
Object.factory()
用全名構造對象.