确定性。举个例子,如果你的MCU正在控制一个火箭的引擎,就在这时,如果一个对栈空间的申请比平常多消耗了几百毫秒,导致对阀门的错误计时,就会发生大爆炸。
还有一些原因致使动态内存分配对嵌入式编程没有多大用。例如,许多使用堆的语言同时也拥有垃圾收集机制。垃圾收集机制经常会暂停整个程序一会儿,在堆上寻找垃圾(不再被程序使用的内存)并清除它们。这比单纯的堆空间申请更加具有不确定性。
好的解决方案:让动态内存分配成为可选项
Rust语言的标准库中有很多特性依赖于堆。然而,Rust语言的编译器支持完全关闭这些有关堆的语言特性,并且能够静态地确保这些特性在程序中不被使用。写出完全不使用堆的Rust程序是完全可行的。
Go语言的解决方案:没有
Go语言严重依赖于对堆的运用。没有可行的方式让Go程序完全不使用堆。这不是Go语言的问题。这在Go语言的目的应用领域完全没有问题。
Go并不是一门实时的语言,通常我们不能担保合理复杂的Go程序的执行时间。这可能有点费解,我来解释一下:Go相对而言很快,但不是实时的,这两个概念非常不同。执行速度快对嵌入式程序来说很重要,但是真正重要的是能否担保某些操作的最大执行时间,而这恰恰是Go不能预测的。这个问题有很大一部分是Go语言对于堆空间和垃圾收集机制的使用造成的。
Haskell也有相似的问题。Haskell同样由于对堆的大量使用而不能胜任嵌入式或者实时编程。然而,我没有看见任何人推荐使用Haskell对机器人编程,所以我不用指出这点。
子问题#2:不安全的底层代码
当我们写嵌入式程序的时候,写一些不安全的代码(不安全的的类型转换,或者指针运算)是不可避免的。在C或C++中,做这样的事情是很简单的。如果我需要向0x1234这个内存地址写入0xff这个值来点亮一个LED,我可以这样写:
(C/C++)
*(uint8_t*)0x1234 = 0xFF;
这样做很危险,只有当我们写非常底层的系统代码的时候才有意义。这就是Go和Haskell没有简单的方式来做这样的事的原因:它们不是系统编程语言。
好的解决方案:将不安全的代码孤立开来
注重安全和系统编程的Rust语言有一个非常好的解决方案:unsafe代码块。unsafe代码块是一种显示地将不安全的代码分离出来的方式。我们通过如下的方式在Rust语言中向0x1234地址写入0xff:
(Rust)
unsafe{ *(0x1234 as *mut u8) = 0xFF; }
如果我们在unsafe代码块外面做这样的事情,Rust的编译器会警告我们。这样允许我们在满足嵌入式编程需要的同时,保持了程序的安全和稳定。
Go的解决方案:没有
Go语言本来就不是为了做这样的事而出现的,所以没有任何内建的支持。
现在你可能会说,“那么为什么你说Go语言不好?这只是一大堆你的抱怨而已。你可以针对任何语言发牢骚。“没有语言是完美的,这很正确。然而,我希望我的抱怨能在某种程度上说明:
英文地址:http://yager.io/programming/go.html?