HOWTO · Arduino
Arduino strcpy 函式
可以使用 Arduino 中的 strcpy() 函式將一個字串從一個變數複製到另一個變數。
本頁內容
使用 strcpy(destination, source) 複製以 null 結尾的 C 字串,連同結尾的 \0 一起複製。目的地必須有足夠空間容納來源中的所有字元及結尾字元。例如,"Hello World" 需要 12 位元組。此函式適用於字元陣列,不適用於 Arduino String 物件;來源與目的地重疊時也不可使用。
認識 strcpy() 與以 null 結尾的字串
C 字串是由字元組成、並以 null 位元組(\0)結尾的序列。strcpy() 接受可寫入的目的地指標,以及指向這類字串的來源指標。它會一路複製到來源字串的結尾字元,並回傳目的地指標。來源可以宣告為 const char *;目的地則必須可寫入。
這個函式不會收到目的地容量,因此無法檢查陣列是否足夠大。若空間不足以容納所有字元和結尾字元,程式行為便未定義。某次執行看起來正常,也不代表目的地太小的複製方式安全。另外,請區分 char name[] 這類 C 字串與 Arduino String 物件;兩者使用不同的介面。
安全地使用 strcpy() 複製字串
這份完整草圖會將 "Hello World" 的 11 個字元及其結尾的 \0 複製到 12 位元組的陣列,再透過 Serial 印出複製後的字串。範例中的字串會以英文 Hello World 顯示,該文字在程式碼中保持原樣。下方的主機端 Serial 替代實作,讓相同的 setup() 邏輯可作為獨立範例執行;在 Arduino 開發板上,Serial 由核心程式庫提供,並由它呼叫 setup()。
#include <cstring>
#ifndef ARDUINO
#include <iostream>
struct SerialPort {
void begin(unsigned long) {}
void println(const char* value) { std::cout << value << '\n'; }
} Serial;
#endif
const char source[] = "Hello World";
char destination[sizeof(source)]; // 12 bytes: 11 characters plus \0
void setup() {
Serial.begin(9600);
std::strcpy(destination, source);
Serial.println(destination);
}
void loop() {
// The copy is performed once in setup().
}
#ifndef ARDUINO
int main() {
setup();
return 0;
}
#endif
Hello World
對這個固定的來源而言,sizeof(source) 會包含結尾字元,因此目的地容量是 12 位元組。若使用指向文字的指標,sizeof(pointer) 只會得到指標本身的大小;此時應以字串長度加一計算所需容量。來源與目的地應使用彼此分開、不重疊的儲存空間。接下來的主機端 C 範例,會在指定的執行環境中示範複製已知字串。
#include <stdio.h>
#include <string.h>
int main(void) {
const char source[] = "Hello World";
char destination[sizeof(source)];
strcpy(destination, source);
puts(destination);
return 0;
}
Hello World
比較 strncpy() 與 strlcpy()
strncpy(destination, source, count) 最多複製 count 個位元組。若來源在 count 個位元組之前就結束,它會以 null 位元組填滿目的地剩餘空間。若來源長度至少有 count 個位元組,它就不會附加結尾字元。因此,若目的地被 strncpy() 填滿,在確認字串已正確終止之前,不可將它傳給字串函式。當目的地陣列容量大於零時,一種常見做法是最多複製 capacity - 1 個位元組,接著設定 destination[capacity - 1] = '\0'。這樣會產生已終止、但可能遭截斷的字串;此做法本身不會驗證來源,也無法避免其他記憶體錯誤。
strlcpy(destination, source, size) 的 size 代表目的地總容量,包含 \0 所需的空間;當 size 大於零時,它會確保目的地以結尾字元終止。空間不足時,它會截斷來源,並回傳完整來源字串的長度;可將回傳值與 size 比較,以判斷是否發生截斷。strlcpy() 並非所有環境都提供:使用前請確認所選 Arduino 核心程式庫與工具鏈是否支援。若不支援,請改用受支援的函式或仔細計算容量後再複製。
將 Serial 輸入複製到字元陣列
這份草圖最多讀取 31 個位元組到 32 位元組的輸入陣列,在回傳的位元組數位置寫入結尾字元,再將產生的 C 字串複製到另一個 32 位元組的目的地並印出。在開發板上,它使用 Arduino 的 Serial API。下方的主機端執行範例會以替代實作提供 Hello World 範例輸入,讓複製邏輯不需要硬體也能執行;顯示的範例文字 Hello World 保持英文原樣。
#include <cstddef>
#include <cstring>
#ifndef ARDUINO
#include <iostream>
struct SerialPort {
void begin(unsigned long) {}
void setTimeout(unsigned long) {}
void println(const char* value) { std::cout << value << '\n'; }
std::size_t readBytesUntil(char delimiter, char* buffer, std::size_t capacity) {
const char sample[] = "Hello World";
std::size_t count = 0;
while (count < capacity && sample[count] != '\0' && sample[count] != delimiter) {
buffer[count] = sample[count];
++count;
}
return count;
}
} Serial;
#endif
const std::size_t INPUT_CAPACITY = 32;
char inputBuffer[INPUT_CAPACITY];
char destination[INPUT_CAPACITY];
void setup() {
Serial.begin(9600);
Serial.setTimeout(1000);
Serial.println("Enter a line:");
std::size_t bytesRead = Serial.readBytesUntil('\n', inputBuffer, INPUT_CAPACITY - 1);
inputBuffer[bytesRead] = '\0';
std::strcpy(destination, inputBuffer);
Serial.println(destination);
}
void loop() {
// This one-shot input example runs once in setup().
}
#ifndef ARDUINO
int main() {
setup();
return 0;
}
#endif
Enter a line:
Hello World
readBytesUntil() 會在讀到 \n、1000 毫秒逾時或讀取 31 個位元組後停止。回傳的 size_t 數量最多為 31,因此設定 inputBuffer[bytesRead] = '\0' 不會超出 32 位元組陣列的範圍;目的地容量相同,足以容納複製的位元組與結尾字元。若一行在換行符號之前就達到 31 位元組上限,該行剩餘內容可能留待下一次讀取。逾時時也可能只回傳部分內容。找到換行符號時,分隔符會一併消耗;但這段程式不會移除它前面的 \r,因此印出的字串結尾可能包含 \r。若應用程式需要移除它,請明確處理。
選擇複製函式並檢查緩衝區容量
複製前,先確認資料是 Arduino String,還是以 null 結尾的字元陣列。對 C 字串而言,容量應以位元組計算,並為 \0 多留一個位元組;UTF-8 文字的字元數與位元組數可能不同。只有在確認來源完整長度能放入目的地時,才使用 strcpy()。使用 strncpy() 時,必須明確處理結尾字元與填補行為;若目標環境提供 strlcpy(),也要檢查回傳值以判斷是否截斷。讀取 Serial 資料時,讀取上限不得超過輸入陣列容量,並應在回傳的位元組數位置補上結尾字元;後續每個目的地也都必須有足夠容量容納產生的字串。