HOWTO · Arduino
Arduino strcpy 関数
Arduino の strcpy()関数を使用して、ある変数から別の変数に 1つの文字列をコピーできます。
このページの内容
strcpy(destination, source) は、終端の \0 を含むNUL終端されたC文字列をコピーします。コピー先には、コピー元のすべての文字と終端文字を格納できる容量が必要です。たとえば、"Hello World" には12バイト必要です。この関数は文字配列用で、Arduinoの String オブジェクトには使いません。また、コピー元とコピー先の領域が重なる場合は使用しないでください。
strcpy() とNUL終端文字列を理解する
C文字列は、文字の並びの後にヌルバイト(\0)が続くデータです。strcpy() は書き込み可能なコピー先ポインターと、そのような文字列を指すコピー元ポインターを受け取ります。コピー元の終端文字までコピーし、コピー先ポインターを返します。コピー元は const char * として宣言できますが、コピー先は書き込み可能でなければなりません。
この関数はコピー先の容量を受け取らないため、配列に十分な空きがあるかを確認できません。すべての文字と終端文字を格納する領域が足りない場合、プログラムの動作は未定義です。一見正常に動作したとしても、容量不足のコピーが安全になるわけではありません。また、char name[] のようなC文字列と、異なるインターフェースを使うArduinoの String オブジェクトを区別してください。
strcpy() で文字列を安全にコピーする
このスケッチは、"Hello World" の11文字と終端の \0 を12バイトの配列にコピーし、その文字列をSerial経由で出力します。下のホスト側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) で得られるのはポインター自体のサイズだけです。その場合は、文字列の長さに1を加えて必要な容量を計算してください。コピー元とコピー先には、互いに重ならない別々の領域を使います。次のホスト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 バイトより短い場合、残りのコピー先領域はヌルバイトで埋められます。コピー元が count バイト以上ある場合は、終端文字を追加しません。そのため、strncpy() によって容量いっぱいまで埋まったコピー先を、終端されていると確認する前に文字列関数へ渡してはいけません。容量が0より大きい配列では、最大 capacity - 1 バイトをコピーした後、destination[capacity - 1] = '\0' として終端する方法がよく使われます。これで終端された文字列になりますが、切り詰められている可能性があります。この方法だけでコピー元の妥当性が確認できるわけではなく、ほかのメモリーエラーも防げません。
strlcpy(destination, source, size) の size には、終端文字 \0 のための領域を含めたコピー先全体の容量を指定します。size が0より大きければ、コピー先を終端します。必要に応じて文字列を切り詰め、戻り値としてコピー元全体の長さを返すため、その値と size を比較して切り詰めの有無を確認できます。すべての環境で使えるわけではありません。使用する前に、対象のArduinoコアとツールチェーンが strlcpy() を提供していることを確認してください。利用できない場合は、サポートされている関数か、容量を慎重に計算したコピー方法を使います。
Serial入力を文字配列にコピーする
このスケッチは、32バイトの入力配列に最大31バイトを読み込み、戻り値のバイト数の位置に終端文字を書き込み、できたC文字列を別の32バイトのコピー先にコピーして出力します。ボード上ではArduinoの Serial APIを使います。下のホスト実行では、ハードウェアなしでコピー処理を実行できるよう、小さな代替実装がサンプル行 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 msのタイムアウトに達したとき、または31バイトを読み取ったときに停止します。戻り値の size_t は最大31なので、inputBuffer[bytesRead] = '\0' と書き込んでも32バイト配列の範囲内です。コピー先も同じ容量のため、コピーしたバイトと終端文字を格納できます。改行に達する前に31バイトの上限まで読み込むと、行の残りが次の読み取りで取得できる状態のまま残ることがあります。タイムアウト時にも途中までの行が返る場合があります。改行区切り文字は見つかった時点で読み取られますが、このコードはその前にある \r を削除しません。そのため、出力文字列の末尾に \r が残ることがあります。アプリケーションで削除が必要なら、明示的に取り除いてください。
コピー関数を選び、バッファー容量を確認する
コピーする前に、値がArduinoの String なのか、NUL終端された文字配列なのかを確認します。C文字列では、\0 の1バイト分を含めた容量をバイト単位で計算してください。UTF-8テキストでは、文字数とバイト数が異なる場合があります。コピー元全体が収まると分かっている場合に限り、strcpy() を使います。strncpy() を使う場合は、終端とパディングをどう扱うかをあらかじめ決めてください。対象環境が strlcpy() を提供しているなら、それを使い、戻り値で切り詰めを確認します。Serialデータは入力配列の容量に合わせて読み取りを制限し、戻り値のバイト数の位置で終端してから、できた文字列が収まるよう後続のコピー先の容量を決めてください。