作者:d作者(w.b)
结合这两篇来一起学习:参数所有权及d的所有权与借贷
内存崩溃:1,致命昂贵的问题.2,很难手动检查.3,由于粗心很容易引入(审查问题)
第1,是缓冲溢出
结束缓冲溢出:1,溢出保护.2,尽量用动态数组而不是原始指针.3,用ref(引用)
而不是原始指针
.4,用常和不变.
结束栈崩溃:1,ref
.2,中
,3,域
.
结束别名问题:转换非指针到指针
,联的其他类型
覆盖指针.
结束分配漏洞
:用垃集
.但我不想用垃集
.1,显式分配/释放
,包括写自己的分配器
.2,资源获取即初化
,即带析构器
.3,引用计数
.
显式分配/释放
内存:1,忘记释放了(泄露内存)
.2,释放后使用
.3,多次释放
.4,光释放,无分配
.
RAII
:1,对域对象
不错.2,其他类型
不友好.
引用计数
如下:
构 S{整*p;}
空 f(引用 S s,引用 S t){
s=S();//消灭s的原先内容
*t.p=1;//崩溃
}
空 主(){
S s;整 i;s.p=&i;f(s,s);//同一指针
}
两个指针同样类型,但其中一个是可变
的.
dip1021
的参数所有权与调用函数.如是可变,则禁止传递超过1个的同一内存对象引用(即多个同一指针)给函数
.再扩展一下:如有可变,则禁止多个引用指向同一内存对象(重要,内存安全的精髓,借贷指针的原则)
.
再讲明白点:允许一个可变引用/多个常引用
,但禁止超过一个可变引用/混杂可变和常引用
.
所有权:一个内存对象
的单可变引用
叫拥有
该对象.
整*f();
整*p=f();//现在,p拥有f()返回的对象
移动(传输)所有权:移动可变引用
时,就传输
了所有权
.原引用
则无效
.
整*p=f();//p是所有者
整*q=p;//现在,q是所有者,p无效了,
*q=3;//可以,所有者修改的权利
*p=3;//错误!无效了,
复制(借用)引用:复制可变引用
借用所有权
,借用完毕,就归还了.用域
来表示借用
.即我借用所有权,是有生命期的,因为他不是我所有的,到时要归还,所以用域表示这是有生命期的
整*p=f();//现在p是所有者
域 整*b=p;//b从p借
*b=3;//借用期间,随便用
*p=4;//对不起,原主人申明要用了,b失效了.
*b=5;//错误,b已经失效了.
当以下情况发生时,借用失效:1,借用引用的最后1次使用
.2,借用引用
离开域
了.3,原主人又用了
.
从开始借用到上面3个之一完
叫借用生命期
.也叫非词法域
.这是通过分析数据流
实现的.
分解函数结构
为一堆
代表从一个块到另一个块的路径
的边
连接起来的代码块
.
对每个块构建数据流等式
:输出=转换(输入)
.(输入,输出
为每个跟踪变量的状态).
解这个方程式:N个方程,N个未知变量
.
创建指针:调用返回指针的函数时,创建指针
.
整*f();//返回所有者指针的函数
整*p=f();//然后,移动至p.
析构指针:移至函数
时,析构指针
.如:
空 g(整*p);
g(p);//p放弃了所有权.这里有块边界流的变化
*p=3;//无效.
悬挂指针:
@活 空 s(){
整*p=f();
}//错误,退出时p是活的.
带所有权函数:
@活 空 g(整*p){
}//错误,p是悬挂指针
@活 空 h(整*p){
g(p);//传输给g了,是g的事情了
}
即指针在跨块间流动(传输)
.而不是仅在块间
.
函数借用指针:
@活 空 m(域 整*b){
}//好,p是域指针,只是借用而已
@活 空 n(域 整*b){
m(b);//没问题,借用后回来了,
g(b);//错误,借用指针逃离(`作用域`)了,即跑到其他地方了,而`域`表示,b是借用的.离开这个区就应该消灭,不再存在了.
}
空 g(整*p);
控制流:
整*p=f();==>p是有效的
整*p=f();==>g(p);==>p是无效的.
p,究竟有效不?
错误,p不能既是有效,又是无效
.
分配和释放
,注意他们不能为@活
.
整*分配();
空 释放(整*);
泄露内存1:
空 f(){
整*p=分配();
}//错误,退出时p是活的.
注意:对语言而言,分配和释放
,没有
啥特殊
行为.即,你可以按第一类公民
写自定义分配器
.
泄露内存2:
整*p=分配();
p=分配();//错误,重写活指针
双释放:
整*p=分配();
释放(p);
释放(p);//错误,p有未定义值
释放后用:
整*p=分配();
*p=3;//使用中
释放(p);//释放了.
*p=4;//错误,p有未定义值
消灭借用指针:
@活 空 m(整*p){
域 整*b=p;
释放(b);//错误,不能把借用指针转成所有者,即不能释放非所有者指针
}//错误,p是悬挂指针.
现在,p
已经归m函数
所管了,所以你不要忘记处理p
了.
常指针:
@活 空 f(整*p){
域 常(整)*c1=p;//借用常指针
域 常(整)*c2=p;//借用常指针
整 i=*c1;//c1,活
整 j=*c2;//c2,活
j=*c1;//c1活
*p=3;//使用p,使`c1,c2`都无效.使用可变指针后,常指针都无效了.因为`一个可变指针/多个常指针`原则,两个不能并存.
i=*c1;//错误,c1无效
j=*c2;//错误,c2无效
释放(p);//释放p.
}
调用函数:
空 b(域 常 整*,域 常 整*);//都是常针
空 b(域 整*,域 常 整*);//不能混合可变与常
@活 空 f(整*p){
b(p,p);//编译
b(p,p);//编译不过
}
回忆引用计数问题:
构 S{//省略引用计数机制
整*p;
}
空 f(引用 S s,引用 S t){
s=S();//消灭s的原先内容
*t.p=1;//崩溃
}
@活 空 主(){//加上@活
S s;整 i;s.p=&i;f(s,s);//在此暴露问题,活暴露问题
}
全局变量:
@活
不能访问全局变量
,只能从参数
引进指针/引用
.
其他指针类型:引用,出,类,隐式本,包装指针,动态数组,闭包,关联数组
.
垃集分配指针
,像其他指针
一样处理,没啥特殊的,不区分.
@活和其他函数
.
@活依赖对接时尊重@活接口的非活函数
,@系统,@安全,@信任
可与@活
对接.因此可渐进
添加活函数
.
异常:1,在块间导致多个复杂边界
.2,当遇见异常控制流
时,放弃优化数据流.3,@活
依赖分析数据流
,因而不能放弃
.因此@活
是不抛
的,这也是我(作者)
推动不抛
作为默认
的部分原因
.
实现:最新的d编译器
的基本原型模式
已经可用了.
结论:1,在已有成功且安全的机制
上构建.2.在机制
保证内存安全
方面的很大进步.3,并不破坏
已有代码,可渐进添加
.