内存安全原理
内存安全、性能、简单性构成一个不可能三角,选择其中两个必定舍弃另外一个。Sric创新性的采用运行时内存安全检查方法,在零开销的同时,减少了内存安全问题。
为什么不用Rust
Sric和Rust都是没有GC的内存安全语言,但两者有很大差别。Rust在编译时做安全检查,而Sric是在运行时检查内存安全。Rust的安全机制有很多编码限制,会强迫用户写复杂的代码。这些复杂代码不但损害可读性,而且往往不是零开销的。Sric的安全机制是无感的,不需要做什么事就能获得内存安全。
可选的内存安全
尽管Sric的内存安全检查开销非常小,但为了实现零开销,最大化性能,默认只在Debug模式下开启安全检查。所以标准的工作流程是在Debug模式下调试好代码,确保没有内存问题后,再编译成Release来部署。
也就是说Sric不是完全内存安全的,安全可以根据项目性质来选择。如果项目对安全性要求超过性能,可以通过编译宏来在Release模式中打开内存安全功能。
对象生命期检查
Sric的内存安全检查包括:数组越界检查、空指针检查、野指针指针检查、悬垂指针检查等。因为使用所有权机制,也不存在内存泄漏、双重释放等问题。其他检查项都比较简单,内存安全核心问题是怎么检测悬垂指针,也就是验证内存的生命期。
在Sric中非所有权指针是一个胖指针,它包括实际指针以及一个检测码等内容。对象内存内部也有一个相同的检测码。在创建指针的时候,指针的检测码和对象的检测码是一致的。当内存释放时,这个检测码被赋0。每次在使用指针时对比指针的检测码和对象的检测码是否相同,如果不同则说明对象内存已经释放掉了。这时候及时报错,不让错误传播到其他地方,这样很容易定位到问题。
虽然原理比较简单,但是需要考虑衍生指针怎么处理(指针指向内存中部,而不是内存头)、内存数组怎么处理、堆上分配和栈上分配怎么处理、非合作对象怎么处理(C++中定义的类没有地方存检测码)。幸运的是Sric已经把大部分问题都解决了。
与Address Sanitizer比较
Address Sanitizer也被用来做内存安全检测。但Address Sanitizer有几个缺点:
- 漏检问题。例如没法检测内存释放以后又被重新分配给其他对象的情况。
- 开销问题。Address Sanitizer的内存占用和运行开销比较大。
- 不能跨平台。对Clang、GCC支持的比较好,其他编译器没有好的支持。有些实现也依赖于ARM CPU架构。
- 依赖的第三方库需重新编译。
Sric工作在语言层面而不是系统层。Sric检查的更全面,开销更少,在任何支持C++的平台上都可以用。