d的分配器2

原文
上篇
我非常确信需要隔离,类似当前语言禁止库作者可追加向量类型,也是可使用向量的主要原因.
一些(GC?)分配器可能有@safe释放函数,但大多数(除了GC?)因为别名不能,这需要隔离.

:
我不是.第一次得到那篇论文链接时,甚至不理解基本概念.相对而言,基于变量借位检查器更容易理解.
总体感觉是在两个场景中使用分配器:
1,受控:这是RC的自包含数据结构类型场景,因为如果非自包含数据结构,Safe无法工作.
2,不受控:无生命期,或为全局的,或只和函数体一起使用,表明胖切片和指针.不安全,(因为像全局变量等)不能生产它.
因此,使内存生命期不受控的安全是无意义的,因为你不应这样!使用数据结构代替.
这只会留下受控的,在此@localsafe是可取的(所以可调用@system版RCAllocator,api).并且对RC用RC(即通过借位检查器消除&顺序),有更好的生命期管理策略.
可抛值类型异常,及ROM相关的RC勾挂.

似乎与dip1021(@live)重复

借位检查器也可工作."隔离"可能更适合D,但它肯定不是唯一选择.
能解释一下@localsafe吗?这和链接线程中Dukc提议有什么不同?

我不是专家,但我读过些隔离文章,这里的一些聪明人喜欢该想法.借用检查器有些类似.使用借用检查器,可有无限常引用或单个可变引用.隔离表明你最多只有一个可变来访问一些数据.所以它缺少借用检查器两者.另一个问题是,处理借位检查器行为时,仿射类型或限定符是否比@live更好.如果有它,是否会要求隔离?

:解释一下@localsafe
除非调用不安全函数,它是@safe的,nogcpure同样..a.
基本上,它验证你没有乱搞,但不限制可调用内容(如回调).
因为用回调创建环境可能会出错,这是我一直想要的.
:另一个问题
是的,对借用检查器,我相信类型限定符()比其他方法都更适合.
本质上,借用检查器只是说,拥有引用生命期必须超过被借用引用生命期,保证了它们正确的析构顺序.
它很简单,在DIP1000中已有大量逻辑来支持它!不想新的语法,只是些围绕所有权/借用更智能的语义.
注意,我不认为@live解决了D社区问题,它是无用的.
没有.没有错误报告.今天没人用它.因为不需要它.

我从来没有见过类似上面"不受控"的东西.使用分配器的方法是:严格要求,是通过灵针/容器.

我写过一些不受控分配器用法.在druntime/phobos中也有很多.毕竟只是已知生命期,但编译器不能证明有用的.查看malloc/free的用法(包括GC内部的用法).
但是,是的,要走的路是某种正常使用受控表示(这需要@live不能解决的借用).

Vector!int vector;
vector ~= 3;

auto borrowed = vector[0];
func(borrowed);

void func(scope ref int value) {

}

基本上,现在忽略了检查借用&函数参数生命期,其他的现在都可做,只是可能不便宜(比如消除RC).

:.a
此时,这与@trusted一样.
问题是,在通用分配器相关容器中,如果写一个@trusted/@localsafe调用RCAllocator.deallocate,就无可阻止某人写带如下deallocate函数的自定义分配器:

struct NaughtyAllocator
{
    // ...

    @system void deallocate(void[] block)
    {
         corruptMemory();
    }
}

然后RCAllocator.deallocate分发该函数,最后在@safe代码中,破坏了内存.

是的,的分配器仍然是的.无法避免.只有地址消毒器可以.
可惜,也无法阻止phoboslibc实现这样.不必在该级别上考虑.无论是错误的还是故意的,内存分配器都可在D编译器无法发现就破坏内存,@safe与此无关.

见过SafeRefCounted使用的borrow方法吗?这里
在当前的D语言中,已可避免容器或灵针泄漏引用.语法很笨拙,因为必须使用回调,但这是可做到的.
生命期问题不是这里的阻碍因素,阻碍因素是可给deallocate/free一个安全接口,这样它就可在未知特定实现的通用多态环境中安全使用.

是的,我知道它.在上一期DConfOnline上,我和RobertSchadek争论过该方法唯一的前进方向.就我而言,这不是前进方向.这是可用性重大倒退,因为它不是人们一般使用数组或数据类型.
不同意该观点.需要让人们远离直接调用分配器!它们是高级概念,很易错,尤其是在创建它们时.就像std.experimental.allocators这样组合它们.
最后,分配器不是@safe是可行的,它们太易错了,在编译器级,很难避免错误.想做更高级事情时,可用它们,而不需要动手.相反使用像向量类型等数据结构来使它@safe,因此生命期是唯一阻塞.

同意这是糟糕用户体验,但还有什么选择呢?在D中实现借用检查器?这需要5-10年的时间,甚至当它完成时也不会正常工作.那时,还不如告诉人们切换到Rust.
我并不主张典型D用户直接调用分配器,从有@safe接口的分配器中受益的人是通用容器和灵针库作者.
目前,编写接受用户提供分配器@safe向量类型是"不可能的",只有在硬编码依赖事先知道行为的特定分配器(或特定的预定义分配器集)的依赖项时才能完成.
如果论点完全放弃支持用户提供分配器,那么可接受这是合理立场.当然,这是最快和最简单的方法来打开容器进展,如果必要,随时重新讨论该问题.

是的,但注意,这确实要求错误的@系统代码.即,与

@trusted void naughtyFunction() => corruptMemory();

差不多,不同在,在分配器中,没有用户提供的有问题的@信任属性.
可要求对引用计数安全分配器必须有已检查了的@trusted属性的

@trusted @disable void refCountCertificate();

成员函数,这样,不写@信任函数,就不可能破坏@安全代码中的内存.

不同在,不能从@safe调用@system代码,但可调用@trusted.在@system代码中,可随意出错,除非有人错误使用@trusted,否则它不会伤害@safe代码.

RichardCattermole的假设中,@trusted(或@localsafe)属性是在容器的实现中;如:

struct Vector
{
    RCAllocator allocator;
    void[] memory;

    // ...

    ~this()
    {
        // ...
        () @trusted { allocator.deallocate(memory); }();
    }
}

这可行,但我认对面对D新手,那就太尴尬了.

其中很多已用DIP1000完成了.
调用函数并传入借用值时,那部分就完成了.
缺少的是最初的借用行为,并保证绑定在另一个对象上.
这只需要在函数体中覆盖,所以这里DFA应该很容易.
可能如下(注意指针类型,不需要ref):

struct Thing(T) {
    ref T get() scope { ... }
}

{
    Thing thing;
    scope ref got = thing.get; // owner = thing
    func(got); // 参数是域.

    thing = Thing.init;
 //错误:`thing`生命期必须超出`got`变量,但正在赋值`thing`.
    return got;
 ///错误:`thing`域变量必须超出`got`,但正在返回`get`.
}

void func(scope ref T value) {}

其他方面,很高兴立场在趋同:)

我同意这会起作用,但我不相信它可为现有域系统上的渐进更改,在完成前,最终必须从重新设计实现域系统.
不过,实现隔离不比工作量小,所以也许它只是个提议.😃

他可更复杂.
got完蛋前,可能已析构thing,语言很难避免.但如果你有事物(thing)指针,并通过它调用析构器?或可同时包含事物

scope SumType!(Thing*, int*)[5]

变量?可解决这些问题,但方法至少比@live更复杂.

现在,想到它时,应该叫它分配器证书,且所有分配器都应用.仅当有一分配原语@系统才是@信任.不必支持最终释放自己内存从而破坏内存分配器,因此不必在引用计数命名证书.

我想这会很复杂.DFA对这种事情总是如此.
是的,需要跟踪内存"真实"物主,并确保正确析构变量.
考虑到@live并不完整,我会说@live@live更复杂;)
唯一区别@live挨个选入函数的,而另一个不是,表明它确实保证,内存安全.

我也怀疑isolated收益与复杂性比及完整借位检查器很差的,它适合Rust,因为它是专门的系统语言,而D应用编程和脚本语言,及系统语言,所以对来说它不是很适合.
毕竟,GC已解决了大部分内存安全问题,而DIP1000可解决大部分(非GC)问题.

注意,D已有一堆特例语言规则,这些规则可通过添加isolated来统一.
如:允许隐式转换所谓的"纯工厂函数"返回值不变,因为程序的其他部分不能引用,调用返回的所有可变内存,即,返回值是隔离的(isolated).

尽管没有明写,对新式(new)也有类似规则.可在下面代码中看到它的作用:

// 从变转为`不变`.
immutable(int[][]) a = new int[][](3, 3);

替换所有这些特例为一组一致的规则简化语言.

使用角度来看,这正是底层类型是个很优秀的DIP的原因.然而,与底层类型一样,编写DIP,实现DIP或调试DIP并不容易.这表明,长远看,可能需要隔离,但是分配器相关引用计数器不应等待.

但那是引用借用函数,而不是分配器.
隔离需要保证没有其他引用指向该数据,以便可无悬针调用释放(free),这也适用于不变,所以可在上放不变数据.

posted @   zjh6  阅读(12)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示