d的分配器3
上一篇2
假定隔离可应用来可变数据或可变数据的指针,如果数据是常或不变的,则D的传递性表明所有指针都是只读的.
我在写带借用和引用计数的(Neat)语言,这不是有效的借用,基本上不想用默认可变变量来借用,因为就需要如下:
Vector!int vector;
vector ~= 3;
void evil() { vector = Vector!int.init; }
auto borrowed = vector[0];
func(borrowed);
void func(scope ref int value) {
//析构最后非借用的向量引用,怎么办
evil;
补充:在rust中,该成语不成立,因为void evil()已抓了向量.但是嵌套函数是D语言的重要部分.因此不能把借用硬塞到围绕垃集设计的语言上;需要在每一个级别上都支持它(就像变量默认为右值一样).
隔离(isolated)会很好,但是现在可用struct来建模它,这样就可:
class Mallocator : IAllocator
{
import core.stdc.stdlib : free, malloc;
void* safeAllocate(size_t n) @trusted
{
return malloc(n);
}
void safeDeallocate(Isolated!(void*) ip) @trusted
{
ip.unwrap.free;
}
}
void main()
{
IAllocator a = new Mallocator;
scope m = a.safeAllocate(4);
auto ip = (() @trusted => assumeIsolated(a.safeAllocate(4)))();
a.safeDeallocate(ip.move);
assert(ip.unwrap == null);
}
工作代码:这里.
隔离可进入std.typecons.
隔离是不够的,还必须保证指针是用'malloc'分配的.
可在用malloc分配的指针外加个包装器.@安全 free(释放)只接受它们,而不接受隔离指针.
不充分的free
如果ptr的值不等于,先前由malloc(),calloc(),realloc()或aligned_alloc()返回的值,则该行为未定义.
未定义行为不是内存安全的.
这太可怕了.还不如叫它DynamicArray,这正是我建议的,用数据结构,而不是直接调用分配器!
当然,可添加@require(AllocatorAware),然后就到此为止.至少会迫使人们审计代码,并发现嘿,这不是我应直接用的,但仍允许传递分配器.
这完全是题外话,显然大多数用户不需要直接使用分配器API.
但是,如果正在*实现*像动态数组等数据结构,并且想支持用户提供的自定义分配器,则数据结构可@safe的唯一方法是分配器API使用该包装器类型来表示@safe接口.
上例中,使用@system可传入想要的任意指针,或提取指针,就失去了给定结构所提供的保护.如果结构很简单,可用@safe很容易地完成,:/所以分配器并不适合返回.所以你只能呆在审计的区间内.
没有吗?
数据结构管理生命期,它从语言中接管来保证检查.
最后,数据结构和算法都要审计.语言应有生命期管理.
这一点上,Dennis做了些工作来替换域推导,它提供期望堵塞从数据结构中借用内存的生命期跟踪中的最后大漏洞的基础设施 😃 ,这里
可用DIP1035的@系统变量来确保不会干扰包装器内部结构.
你没有抓住我原帖要点.
为了使数据结构向用户提供保证,分配器必须依次向数据结构提供某些保证.如果数据结构事先不知道用哪个分配器(如如果它在调用通用RCAllocator.deallocate函数),则必须在类型系统中编码那些保证,以便@safe代码不会意外破坏它们.
可惜,我确实完全掌握了它.我认为不值得.
沃尔特已表明,他不想,要解决此事需要的DFA,因此解决该问题只会浪费精力.
据我所知,不能同时具备以下三个条件:
1.@安全容器.
2.用户提供分配器.
3.语言不变.
考虑到Walter和Atila在内存安全的立场,(1)是必须的,所以问题是更喜欢(1)+(2)(实现隔离等)还是(1)+(3)(禁止用户定义自己的分配器),我也不确定哪个更好.
你一会儿说(1)+(3),我提出(1)+(2),你说(1)+(2)+(3)是可能的.
如果同意我说的(1)+(2)+(3)是不可能的,则我不反对你的其他主张;另一方面,如果你相信(1)+(2)+(3)是可能的,则想听听你的想法.
改变语言来帮助解决该问题,如果有机会,不会不这样做;)特别是小改变,且有多个用例时.
现在,1.和2.我想区分有效的@safe和机器可检查的@safe.
我不相信会有完全的机器检查能力.这表明DFA,人手不足以来设计和实现它.DIP1000是个很好的示例,因为它不支持变量中间接有多个生命期.
有效@safe表明尽量多的机器检查代码,但是希望隔离不安全部分到库代码中,在那里审计来确保安全.
告诉人们不要用自定义分配器.
是的,这是不言而喻的,即使有像隔离限定符这种语言特性来帮助,数据结构和分配器仍需要在内部使用@trusted代码.
我无法理解.你是在建议(1)+(3),(2)+(3),还是两者的混合?比如,如果使用官方分配器,容器将是@safe的,如果使用第三方的分配器,它将是@system的?
如果人多,则是1和2(但不是3).否则,就只能暂时把目标从完美变成足够好.正如安德烈所说:完美是好的敌人,似乎有条相当不错的"好"路.
使用什么分配器库并不重要.它工作或不工作.编译器不应关心是谁的代码,只应该关心生命期模式.
你建议目标在哪.
我建议,尽量多地用@safe检查,但是让数据结构尽量多地负责分配器的生命期保证.
完美方法:所有代码都是机械检查的.
好的:审计库和底层代码,机械检查库用户代码.
先有个好方法,再试找出是否可消除限制,并在库代码应用.
我是一半一半.像@localsafe表明API中的分配器都应该是@system.因为该线程,我重新考虑我自己的分配器.
但是就编译器硬编码进std.allocators对比sidero.base.allocators,是的,不应这样,如果两个API匹配,则编译器应该在生命期管理方面以相同对待它们.
前面我提出了分配器中要求1个@信任的证书函数.你同意原则上满足所有这些要求,但不会考虑它,因为向外人解释会很尴尬.你能解释一下吗?也许有更多的想法.
无论是语言原语还是标准库函数都给予了保证,重要的是不涉足@系统或@信任,用户可写什么.
标准库可能有漏洞,语言级检查器也可能有漏洞.
根本问题是没有(除了约定外)强制机制来阻止分配器拥有@信任并瞎搞.特别是,在初始实现分配器,库作者都必须(文档?注解?)“知道”@系统是否符合@信任证书.
手动验证@系统代码是在D中要远离的(参见DIP1035),采纳该机制是个倒退.
则,应该允许@safe数据结构调用@system分配器,并简单*假设*分配器实现不会乱搞吗?如果有人编写了自己的分配器实现,且没有手动检查他们的@system代码是否提供了数据结构所期望保证,可接受他们在@safe代码中得到内存破坏吗?
问题是,如果编写@safe数据结构
1.需要分配器为你提供某些保证(即,只要满足前提条件A,B和C,调用deallocate就是安全的).
2.如果接受用户提供的任意分配器,则无法判断它们是否提供了这些保证.
忘记分配器的作者.数据结构作者应该做什么?
可能答案是,“数据结构作者应该假设提供了保证”.该答案是不可接受的,因为它允许在@safe代码中内存破坏.
或,“数据结构作者应该在分配器上找到某种"标志"或"证书",以表明它提供了必要的保证”.这是个更好的答案,但并不理想,因为它需要手动验证@trusted代码及@system代码,并且在数据结构作者和分配器作者之间还要划分验证责任.
或,"数据结构作者应该创建白名单,列举他知道提供必要保证的分配器,并且只信任该名单上的分配器."这与前面答案类似,但是验证的所有责任都放在@trusted代码的作者身上(即数据结构作者),并且不依赖于分配器作者来手动验证他们的@system代码.
或,"数据结构作者不应接受用户的任意分配器."这是限制性更强的折衷方案,但与前面不同,它允许数据结构作者确信他的@trusted代码不会导致内存破坏.
或,"数据结构作者应该依靠分配器作者来提供@safe接口."这是最好的答案,但需要添加新的语言特性,不行.
我想听到你的回答.你认为该数据结构的作者应该怎么做?
除非Walter改变他对DFA的看法,或说让在dmd上增加校对引擎,否则没法.
现在有了@系统变量,所以就像ntrel和Dukc已提出的那样,可有poorman的typestate和poorman的move语义.
为什么该机制不行呢?这不正是"@系统"变量要解决的问题(非平凡内存安全不变量)吗?
(用sumtype,甚至可移动标志到运行时(以标志数量呈指数级膨胀为代价)来获得poorman的依赖类型状态.)
import core.stdc.stdlib;
void main(){
import std.stdio;
void foo()@safe{
auto ptr0=fancyMalloc(16);
writeln(ptr0.borrow((scope ptr){
return cast(int)ptr;
}));
fancyFree(ptr0);
}
foo();
void bar()@safe{
auto ptr0=fancyMalloc(16);
auto ptr1=ptr0.withAliasing.leak;
// 好,泄漏是安全的
writeln(ptr1);
}
bar();
void baz()@safe{
auto ptr0=fancyMalloc(16);
auto ptr1=ptr0.withAliasing;
// fancyFree(ptr1); // 错误,未隔离
}
baz();
void qux()@safe{
auto ptr0=fancyMalloc(16);
auto ptr1=ptr0.withAliasing;
// auto ptr2=ptr1.unsafeAddFlags!(PointerFlags.isolated); // 错误,不安全
// fancyFree(ptr2); // 好
}
qux();
void flarp()@trusted{
auto ptr0=fancyMalloc(16);
auto ptr1=ptr0.withAliasing;
auto ptr2=ptr1.unsafeAddFlags!(PointerFlags.isolated); // 好,可检查它是否工作
fancyFree(ptr2); // (ok)
}
flarp();
void bongo()@safe{
auto ptr1=function()@trusted{
auto ptr0=malloc(16);
return ptr0.unsafeAddFlags!(PointerFlags.mallocd|PointerFlags.isolated);
// 可以看出它是`malloced`和`隔离`的
}();
fancyFree(ptr1); // ok
}
bongo();
}
enum PointerFlags{
none,
mallocd=1,
isolated=2,
}
struct Pointer(T,PointerFlags flags){
private @system T* ptr;
Pointer!(T,flags&~PointerFlags.isolated) withAliasing()@trusted{
auto result=ptr;
ptr=null;
return typeof(return)(result);
}
static if(!(flags&PointerFlags.isolated)){
T* leak()@trusted{
auto result=ptr;
ptr=null;
return result;
}
}
auto borrow(R)(scope R delegate(scope T*)@safe dg)@trusted{
scope local=ptr;
ptr=null;
scope(exit) ptr=local;
return dg(local);
}
auto borrow(R)(scope R delegate(scope T*)@system dg)@system{
scope local=ptr;
ptr=null;
scope(exit) ptr=local;
return dg(ptr);
}
}
Pointer!(T,flags) unsafeAddFlags(PointerFlags flags,T)(ref T* ptr)@system{
auto result=ptr;
ptr=null;
return typeof(return)(result);
}
Pointer!(T,newFlags|oldFlags) unsafeAddFlags(PointerFlags newFlags,T,PointerFlags oldFlags)(ref Pointer!(T,oldFlags) ptr)@system{
auto result=ptr.ptr;
ptr.ptr=null;
return unsafeAddFlags!(newFlags|oldFlags)(result);
}
Pointer!(void, PointerFlags.mallocd|PointerFlags.isolated) fancyMalloc(size_t size)@trusted{
return typeof(return)(malloc(size));
}
void fancyFree(ref Pointer!(void, PointerFlags.mallocd|PointerFlags.isolated) ptr)@trusted{
if(!ptr.ptr) return;
free(ptr.ptr);
ptr.ptr=null;
}
浙公网安备 33010602011771号