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
的,nogc
和pure
同样..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
代码中,破坏
了内存.
是的,差
的分配器仍然是差
的.无法避免.只有地址消毒器
可以.
可惜,也无法阻止phobos
或libc
实现这样.不必在该级别上考虑.无论是错误
的还是故意
的,内存分配器
都可在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)
,这也适用于不变
,所以可在堆
上放不变
数据.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现