d静态数组生存期
如下代码非期望:
int[] foo()
{
int[1024] static_array;
// return static_array[];
//返回`'static_array[]'`时逃逸了局部变量`'static_array'`的引用
return null;
}
class A
{
this(int[] inData)
{
data = inData;
}
int[] data;
}
void main()
{
int[] arr;
A a;
{
int[1024] static_array;
arr = aSlice; // OK
a = new A(aSlice); // OK
arr = foo();
//arr = foo();
}
}
通过赋值aSlice给arr或a,它似乎逃逸了域,我以为会有错误,但代码编译得很好.
真的安全吗?
切片是原数组拥有的现有内存的视图.切片不会分配内存.GC跟踪动态数组,分配的内存的所有引用,因此只要切片活跃,则原内存也活跃.
在栈上分配静态数组,它们离开函数域时就失效了.同样,引用栈内存的切片或其他指针也是如此.
这里重要的不是域,而是栈.内部域中分配的内存使用函数栈,因此在退出函数前,所有内存都是有效的.
不,它不安全.可在程序头加@安全:行,(在重命名static_array为aSlice后),它无法编译:
test.d(27):错误:赋值'aSlice'变量的地址给具有较长生命期的'arr'.
默认,假定所有内容都为@system.
退出域时,会调用析构器,等等.
谢谢,@safe对第一片代码管用,但是下面代码仍然可编译:
class A
{
@safe
this(int[] inData)
{
data = inData;
}
int[] data;
}
@safe
int[] foo()
{
int[1024] static_array;
//返回`'static_array[]'`时逃逸了局部变量`'static_array'`的引用
return null;
}//.1
@safe
A bar()
{
int[1024] static_array;
return new A(static_array[]);
}//.2
@safe
void main()
{
auto a = bar();
writeln(a.data); // OK, 但写的是垃圾
}
所以编译器在foo()中检测逃逸,而在bar()中没有,这不对.
是否可区分切片是来自动态数组还是静态数组?
编译器可用-dip1000开关来检测它.
为了调试?也许找到栈边界,检查地址是否在栈中?
.1对编译器来说是很简单的.
.2需要更深层次分析代码.是的,传递了切片给构造器,但是不知道构造器是否会存储该切片,或只是使用它.即使下面代码是安全的,但编译器不能检测:
class A
{
@safe
this(int[] inData)
{
data = someCondition() ? new int[42] : inData; // (1)
// ...
if (someOtherCondition()) {
data = null; // (2)
}
}
// ...
}
(1)根据someCondition(),确定是否使用inData,可能根据程序中的someCondition(),根本不会调用bar().
(2)构造器退出后,根据someOtherCondition(),数据可能不会引用"static_array"
编译器很难看穿.(@live也许有帮助.)此外,"单独编译"使它看不穿函数边界.
此外,不希望编译器强制复制所有栈变量.
D的安全模型一样.在@安全代码中,D拒绝编译器不确定内存安全的内容.然而,与Rust中不同的是,@安全在D中不是默认的,因此要手动标记.
@safe``删除破坏内存.@live正在通过跟踪数据活跃度来改善.
@安全的目标是确保在@安全代码中不能破坏内存,仅在@系统或@信任中间断出现,如果文档没说不清楚,就是文档的问题.
但是,存在一些需要重大更改才能@安全的已知的问题,并且为了使现有代码更易迁移,这些更改隐藏在dip1000下,因此实践中,如果不带-dip1000的使用@安全,可能会遇见危及内存安全的编译器错误.
你的示例,就是这样.不带-dip1000,应禁止@安全代码中切片栈分配的静态数组,但由于存在漏洞,编译器允许它,因此应由-dip1000开关修复该错误.
浙公网安备 33010602011771号