下面从 基础设施、C++17 标准库 两个方面对 C++ 合约语言的一些特性进行阐述。
基础设施
与标准C++ 相比,从安全与审计角度考虑,不推荐使用以下基础设施:
指针。指针的越界行为是 C++ 中最难以捉摸的行为,因此审计时需要格外小心。
数组。数组的越界是 C++ 中的常见错误且很难排查。合约语言从正交化的角度考虑提供
std::vector
或std::array
来替代。全局变量与静态成员变量。全局对象和静态成员对象的构造与析构是在合约开始和退出的时候执行的,并且执行顺序得不到保证,使用时很容易出错。
在 C++ 中,常见的基础设施还包括重载、模板与继承,合约语言对这些基础设施支持良好,且允许组合使用。
C++17 标准库
鉴于 C++ 在内存管理、进程控制、文件使用等方面强大但不符合区块链行为定义的能力,合约语言对 C++ 做了很多限制与改造。
标准库支持与系统调用封装
智能合约平台对合约语言的标准库支持边界定义如下:
malloc/free、new/delete 等内存管理类操作。已改写以保证安全性。
abort/exit 等进程控制类操作。已改写以保证安全性,不应在合约中使用。
iostream/cstdio 中所包含的 IO 操作。合约语言不允许进行类似操作,同时提供了与 C++ 中
printf
行为相仿的print
接口供合约开发者本地调试与输出使用。参见 print 接口说明 了解详情。不支持随机数。
合约语言不支持任何系统调用,因此用户在使用标准库时也不能直接或间接依赖于底层系统调用。与C++17标准库相比,合约平台不支持以下操作。
分类 |
细分说明 |
流操作 |
fstream/iostream |
文件系统 |
filesystem |
位操作 |
bit |
线程与异步 |
std::thread std::future |
不支持的 C++ 特性
合约语言所基于的 WebAssembly 技术当前阶段不支持异常、线程、浮点数-整数转换、RTTI 等特性。因此,您在开发时无法使用上述特性来开发合约。
编译工具使用以下编译参数对某些 C++ 特性做了限制,编写合约时注意不要使用被禁止的特性:
参数选项 |
说明 |
-fno-cfl-aa |
禁用 CFL 别名分析 |
-fno-elide-constructors |
禁用复制构造函数的 复制消除行为 |
-fno-lto |
禁用链接时优化 |
-fno-rtti |
禁用 RTTI |
-fno-exceptions |
禁用异常 |
-fno-threadsafe-statics |
禁用静态局部变量的线程安全特性 |
未定义行为
C++ 语言标准为了给编译器提供更大的优化空间,把许多不符合规范的代码行为都归类为未定义行为。当您的代码中出现未定义行为时,编译器可能认为程序的正确性已经失去保证,从而实施一些十分激进的优化。未定义行为会导致程序运行时出现难以理解的逻辑错误,您在编写合约代码时需要十分谨慎。
以下列出了 C++ 中一些常见的未定义行为。
基础操作
越界访问
int a[4]; int i = a[4];
访问未初始化变量
int i; std::cout << i;
整数运算
有符号整数溢出
int i = INT_MAX + 1;
非法位移运算
int i = 1 << -1; int j = 1 << 32;
非法数学运算
int i = 1 / 0;
指针操作
访问空指针
int* p = nullptr; *p = 0;
访问分配空间为 0 的指针
int* p = new int[0]; *p = 0;
访问已被释放的指针
int* p = new int; delete p; *p = 0;
指针运算产生的结果越界
int a[4]; int* p = a; int* q = p + 4;
非法指针类型转换
float f; int* p = (int*)&f; *p = 0;
使用
memcpy
拷贝有重叠的数据段int a[4]; memcpy(a, a, sizeof(a));
推荐使用的编译选项
C++ 编译器提供了一些选项可以检查许多不正确的或危险的代码写法,建议您在编译智能合约时把这些选项都打开。
选项 |
说明 |
-Wall |
对可疑的代码写法提出告警 |
-Wextra |
在 -Wall 的基础上提供一些额外的检查 |
-Werror |
把所有的告警当做编译错误 |
运行时资源限制
栈空间
合约执行过程中,栈空间的限制为8K。如果您的合约执行过程中,用的栈空间超过这个值,那么合约最终会被强制异常终止,错误码为10201。所以请慎重使用深度递归和过大的栈上变量。
内存空间
合约运行过程中,最多可用的内存空间为16M,如果合约运行过程中所需内存超过这个限制,则合约会被强制异常终止,错误码为10201。
说明:内存空间默认为16M,如有更改需求请联系管理员。
合约间调用深度限制
一个合约A调用合约B,合约B又可以调用合约C,以此类推。这种合约之间的调用深度最多不能超过1024,如果超过了这个限制,那么最后被调用的合约会产生异常,错误码为VM_STACK_OVERFLOW即10002,请您在写合约时留意。