strcpyを自作する
代入では文字列は写らない
前回で、文字列の終わりが '\0' で決まっていることを見ました。今回はその文字列を別の場所へ写します。
まず、うまくいかない書き方から確かめます。
char name[64] = "Tanaka";
char copy[64];
copy = name; /* コンパイルエラー */配列そのものを代入することはできません。では次はどうでしょうか。
char *p = name;これは通りますが、写したことにはなりません。p は name の先頭を指しているだけで、中身は1バイトも増えていません。p[0] = 'X'; と書けば name のほうが変わります。同じ実体を2つの名前で見ているだけだからです。
本当に別の場所へ持ちたいなら、1バイトずつ運ぶしかありません。それをやってくれるのが strcpy です。
strcpy の中身
void my_strcpy(char *dest, const char *src) {
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
*dest = '\0';
}やっていることは、読む側と書く側の2本のポインタを同時に進めながら、1バイトずつ写すことだけです。前回と同じ歩き方が、今度は2本になっただけとも言えます。
見落としやすいのが最後の *dest = '\0'; です。while は src が終端に来た時点で抜けるので、終端そのものは写されていません。ここを書き忘れると、copy の中身は正しく並んでいるのに終わりの印が無い状態になり、表示すると後ろのごみまで続けて出ます。しかも運が良ければ動いてしまうので、気付きにくい壊れ方をします。
dest に const が付いていないのは、そこへ書き込むからです。src は読むだけなので const を付けます。引数の型を見るだけで、どちらが写す先かが分かるようになっています。
写った先は独立している
コピーが本物のコピーなら、片方をいじってももう片方は変わらないはずです。逆に言えば、写した先を書き換えて元が変わってしまったら、それは写せていなかったという証拠になります。今回の課題では、コピーしたあとに1文字だけ書き換えて、元が無事かどうかを目で確かめます。
写す先は自分で用意する
my_strcpy は写す先の領域を作りません。すでにある器へ運ぶだけです。ですから呼ぶ側が、写すものが収まる広さの器を先に用意しておく必要があります。前回の話をここで使うと、必要なのは strlen(src) + 1 バイトです。終端のぶんを足すのを忘れると、文字は全部入るのに終わりの印だけがはみ出します。
なお、strcpy は写す先の広さを一切見ません。dest が足りなくても、src の終端まで書き続けます。関数の側からは器の広さを知りようがないからです。ポインタは先頭の番地しか持っておらず、そこから何バイト使ってよいかという情報はどこにも付いていません。この「広さを知らないまま書き込む」という性質が、次回のバッファオーバーフローの正体です。まずは仕組みを手で書いてみましょう。
要件
- my_strcpy という関数を自分で書く。第1引数が写す先、第2引数が写す元
- 写し終わったあとに終端のヌル文字を必ず置く
- 1行目に コピー Tanaka の形で写した結果を表示する
- 2行目に 書き換え Xanaka の形で、写した側の先頭を 'X' にした結果を表示する
- 3行目に 元 Tanaka の形で元の名前を表示する
入出力例
main("Tanaka") → "コピー Tanaka
書き換え Xanaka
元 Tanaka"
main("Suzuki") → "コピー Suzuki
書き換え Xuzuki
元 Suzuki"
main("Ito") → "コピー Ito
書き換え Xto
元 Ito"