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()");
}

 

posted @ 2025-01-27 00:26  mcwhirr  阅读(645)  评论(0)    收藏  举报