arduino简单string入门——String与char[]
堆栈和堆使用相同的可用内存空间。堆栈从高内存向下增长,堆从低内存向上增长,见上图。当它们重叠时,您将耗尽内存,并且代码将无法按预期运行。
使用方法本地字符串而不是固定的 char[] 基本上只是将一种内存使用类型换成另一种。也就是将字符串堆换成 char[] 堆栈。例如,在 UNO 和 Mega2560 上,两者之间的区别
void localMethod() { char test[128]; test[0] = '\0'; . . . }
它使用 128 字节的堆栈,并且
void localMethod() { String test; if (!test.reserve(127)) { String.println(F("Out-Of-Memory")); } . . . }
它具有 128 字节的堆缓冲区(127 + 1 用于终止 null)和 8 字节的 String 类堆栈,只有 8 个字节。
因此,使用本地字符串而不是 char[] 会占用大约 8 个字节的 SRAM,但可以为您提供边界检查和缓冲区溢出保护,并带来许多代码便利。字符串分配实际上更安全,因为在堆栈上分配 char[] 时无需检查。而创建字符串时,如果没有剩余内存,则不会使用堆内存。您可以检查reserve( )的返回值 以查看是否有足够的内存。在本地堆栈上分配本地 char[] 时,这是不可能的。同样,在 UNO 和 Mega2560 上,使用本地字符串非常安全,因为当程序尝试分配缓冲区时,它始终会保留 128 个字节供堆栈使用。如果没有额外的 128 个字节可用于堆栈,则字符串分配失败。
为什么不应该使用 c 字符串方法(例如 strcat、strcpy 等)或 char[] 操作
30 多年来,C 字符串方法一直是程序员的祸根,也是许多编程错误和安全漏洞的根源,因此 微软禁止其程序员使用它们(本地副本在此处),并且已经编写了有关不应使用它们的原因的教科书。例如,Robert C. Seacord 撰写的《C 和 C++ 安全编码》第 2 版第 5 章 (本地副本在此处)讨论了特定 c 字符串方法的危险,第 2 章 (本地副本在此处)讨论了出现编码错误是多么容易。对 char[] 的低级操作(一次一个字符)也是主要的错误来源。黑客喜欢使用 c 字符串方法和 char[] 代码,因为相关的编码错误使他们能够强制缓冲区溢出并访问计算机系统。请参阅Wikipedia Buffer_overflow 条目,有关使用 c 字符串和 char[] 操作的商业编码错误的最新示例,请参阅Unix 的 sudo 代码中的“错误”。最近的 IPhone 安全漏洞是另一个近期的例子。Linux中还有更多的缓冲区溢出和未终止的 char[]。
自 C++ 1.0 发布以来,已经有许多 String 类的实现,以克服使用 c 字符串方法时出现的系统性编码错误。std::string 在 C++98 中已标准化。Arduino有自己的 String 类版本,本教程将介绍该版本。
作为使用 c 字符串方法出现的问题的真实 Arduino 示例,请参阅此Arduino 论坛帖子(本地副本在此处)。
缓冲区溢出概述 – c-string、String 和 SafeString
编写一个展示 C 字符串编码错误的小程序很简单。专注的 C 程序员抱怨这只是用户编码错误,可以通过仔细编码来修复。然而,30 多年的经验表明,这些类型的编码错误非常非常常见,而且很难找到。编码错误通常隐藏在特定程序序列运行或输入数据填充 char[] 之前。有关生活示例,请参阅 Arduino 论坛上的这篇帖子使用字符串 char 导致 Arduino 代码重新启动
c-string / char[] 替代方案
考虑以下草图 bufferOverflow_ex1.ino
void setup() { . . . } void appendCharsTo(char* strIn) { // 应该在这里检查边界,但是.. // i) 无法从 char* 判断 char[] 有多大 // ii) 似乎没有人添加边界检查代码。 strcat(strIn, " some more text"); Serial.print(" appendCharsTo returns:"); Serial.println(strIn); } void loop() { Serial.println("--------- start of loop()"); char str1[24] = "some str1"; // 为 strcat 留出额外空间 char str2[] = "some str2 other text"; appendCharsTo(str1); Serial.print("str1:"); Serial.println(str1); Serial.print("str2:"); Serial.println(str2); Serial.println("--------- end of loop()"); }
在 UNO 上运行此草图时,它似乎按预期工作。但是,在 ESP8266 上运行它时,str2 被清除,而在 ESP32 上,草图会反复重新启动。从 ESP32 堆栈转储中不难找到错误,但在 UNO 上,您甚至不会寻找错误,而在 ESP8266 上,导致 str2 为空的原因并不明显。
Arduino 字符串替代方案
更改为 Arduino 字符串很简单,完全避免了错误,bufferOverflow_ex2.ino
void setup() { . . . } void appendCharsTo(String & strIn) { strIn += " some more text"; Serial.print(" appendCharsTo 返回:"); Serial.println(strIn); } void loop() { Serial.println("--------- 循环开始()"); String str1 = "some str1"; // 注意:不建议在 loop() 中声明字符串,因为它会导致内存碎片 String str2 = "some str2 other text"; appendCharsTo(str1); Serial.print("str1:"); Serial.println(str1); Serial.print("str2:"); Serial.println(str2); Serial.println("--------- 循环结束()"); }
使用 Arduino 字符串时,str1 会自动扩展以存储附加文本。在bufferOverflow_ex2.ino中, str1 的扩展会在堆中留下一个空洞,但这不会阻止程序正常运行,并且使用本教程顶部的指南可以完全避免内存碎片。
SafeString 替代方案
您还可以使用SafeString 库作为 Arduino 字符串的替代方案。在这种情况下,SafeString 包装 char[] 并保护它免受缓冲区溢出和越界索引的影响。基本上添加了熟练、专注的程序员会添加的所有检查代码。bufferOverflow_ex3.ino
#include <SafeString.h> // install the SafeString library V3.1.0+ from Arduino library manager or // download the zip file from https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html void setup() { . . . } void appendCharsTo(SafeString& strIn) { // pass strIn as a reference & strIn += " some more text"; // this does all the bounds checks Serial.print(" appendCharsTo returns:"); Serial.println(strIn); } void loop() { Serial.println("--------- start of loop()"); char str1[24] = "some str1"; // allow extra space for appendCharsTo char str2[] = "some str2 other text"; createSafeStringFromCharArray(sfStr1, str1); // or cSFA(sfStr1,str1); for short. Wrap str1 in a SafeString appendCharsTo(sfStr1); if (SafeString::errorDetected()) { // set true if any SafeString has an error. Use hasError() on each SafeString to narrow it down or use SafeString::setOutput(Serial) for error msgs Serial.println(F("Out of bounds error detected in appendCharsTo")); } Serial.print("str1:"); Serial.println(str1); Serial.print("str2:"); Serial.println(str2); Serial.println("--------- end of loop()"); }
浙公网安备 33010602011771号