HOWTO · Arduino
Arduino 中的 memcpy 和 memmove
可以使用 memcpy()和 memmove()函数将一个内存块从一个变量复制到另一个变量。
本页内容
Arduino 程序可以使用 C 标准库函数 memcpy() 和 memmove(),在内存位置之间复制字节。源区域与目标区域不重叠时,选择 memcpy();两者可能重叠时,选择 memmove()。这两个函数都会复制调用者指定数量的字节,而且都不知道目标缓冲区的大小,因此程序必须确保目标缓冲区有足够空间。
使用 memcpy() 复制字节
函数签名为 void *memcpy(void *dest, const void *src, size_t n);。dest 参数指向要写入的第一个字节,src 指向要读取的第一个字节,n 表示字节数,而不是数组元素个数。对于数组,只要数组仍在作用域内,sizeof(array) 就会给出数组的总字节数;对于指针,sizeof(pointer) 给出的是指针本身的大小。
源区域和目标区域不得重叠。只要有一个字节同时属于两个区域,调用 memcpy() 就会导致未定义行为:C 和 C++ 语言规则不保证任何特定结果。因此,不能断言这种调用一定会溢出或以某种特定方式破坏数据。常规复制时应使用独立的目标缓冲区,并确保其中至少有 n 个可写字节。该函数不会分配内存,也不会检查容量。
使用 memmove() 处理重叠区域
void *memmove(void *dest, const void *src, size_t n); 接受相同的参数,并复制相同数量的字节。与 memcpy() 不同,源区域和目标区域重叠时,使用它仍有明确规定的行为。其结果相当于先保留原始源字节,再将这些字节写入目标位置,因此适合在同一个数组中移动其中一部分内容。
这一保证只解决区域重叠的问题。memmove() 仍会从 dest 开始写入 n 个字节;它无法确定指针所指对象的大小,也无法阻止越界写入。调用前要检查目标区域的可用空间,并确认源区域中有 n 个有效字节。
如何选择 memcpy() 或 memmove()
已知两个区域彼此独立时,使用 memcpy()。这能直接表达较简单的情况,也可能让实现采用针对非重叠区域优化的复制方式。只要区域可能重叠,或两者之间的关系不确定,就使用 memmove()。不要依赖某个平台上重叠区域的 memcpy() 看起来能够正常工作;编译器优化会让未定义行为尤其难以预测。
无论使用哪个函数,都应先确定字节数和目标区域的容量。复制整个数组时,应趁它仍是数组时使用其 sizeof 值,并确保目标区域至少能容纳这么多字节。如果复制的元素类型不是 char、unsigned char 或 std::byte,应将元素个数乘以 sizeof(element),并确认这种操作适用于该类型;这些函数复制的是对象表示,不会逐个转换或赋值数组元素。
Arduino 示例:复制数组
下面的独立 C++ 程序演示了将数组复制到另一个缓冲区,以及在同一个缓冲区内移动数据。它使用的标准库调用也适用于 Arduino C++ 代码。在开发板上,可以将复制操作放在 setup() 中,再用 Serial.println() 将目标内容发送到串口监视器;目标缓冲区的大小必须足以容纳要复制的字节。如果要将字符串作为 C 字符串打印,还要包含结尾的空字符。
使用标准 C++ 编译器运行这个完整示例:
本节中的代码以及显示的标签或输出仍为英文。
#include <cstdio>
#include <cstring>
int main() {
const char source[] = "LED";
char destination[sizeof(source)] = {};
std::memcpy(destination, source, sizeof(source));
std::printf("%s\n", destination);
char buffer[] = "ABCDE";
std::memmove(buffer + 1, buffer, 4);
std::printf("%s\n", buffer);
return 0;
}
LED
AABCD
这里的 destination 大小刚好足以容纳源数组,包括结尾的空字符。第二次复制会在 buffer 内将四个字节向右移动一个位置;目标区域与源区域重叠,因此应使用 memmove()。示例输出中的 LED 和 AABCD 字符串仍以英文显示,因为代码块按原样保留。在 Arduino 草图中,照常包含 Arduino.h,并在调用 Serial.begin(...) 后用 Serial.println() 替换桌面程序中的 std::printf() 调用。内存复制调用及其容量要求保持不变。