事の起こり
先日、同僚からC++のstringオブジェクトっているの?という質問を受けました。そこで、今回は私の考えるstringオブジェクトのありがたみについて書いて見ようと思います。
stringを用いたありがたいコード
C++のstringオブジェクトを使うと下記のようなありがたいコードが書けるのです。なんと、文字列の代入ができるのです!! ・・・
これだけ!? 何がありがたいの? と思われる方もいると思うので、経緯を説明していきますね。
string str;
str = "aaaaa"; //代入できる!!!
c言語の文字列は代入ができないという話
下記コードのように描けば、文字列の代入くらいC言語でもできるでしょ と思われる方もいらっしゃるかもしれませんが、残念ながらできません。それは、なぜか
char str[5];
str = "aaaa";//Array type 'char [5]' is not assignable
少し考えれば分かりますが、上記のコードのstrは文字列strの先頭アドレスを示しています。したがって、str = "aaaa"というのは、アドレス = 文字列となってしまっていてC言語の構文的におかしく、代入はできません。
じゃあ、どうするのか・・・ strcpyの出番です!!
もはや脆弱性の代名詞になってしまったstrcpy君
C言語では、文字列の代入を行う時はstringライブラリのstrcpy関数を使用します。
char str[5];
strcpy(str,"aaaa");
printf("%s",str);//aaaaと表示される
以外と簡単じゃないか、stringなんて必要ないなと思っていた時期が私にもありました。実はc言語のstrcpyは非常に危険なんです。例えば、上のコードを少し書き換えて、文字列の要素数を5から3に変更します。
char str[3]; //配列の要素数を変更
strcpy(str,"aaaa");
printf("%s",str);//aaaaと表示される
こうするとなんと!!!・・・ エラーが起きないんですね。(環境によっては警告が出ます。)でも実際は、文字列strのサイズ(この例では3)を超えた領域に書き込みを行うバッファオーバーランが発生しているのです。サイズを超えた領域はどこか?? なんてことは誰にも分かりません。奇跡的に問題ないかもしれませんが、書き換えてはいけない領域を書き換えてしまうこともあります。(制御系のソフトウェアだと一発アウトですね)
このように、strcpyは大変危険な脆弱性を孕んだ関数なわけです。実際、情報セキュリティ系の資格試験でもstrcpyの脆弱性について出題されたことがあるくらいです。この危険を避けるために、strncpyやstrlcpyなどがあります。しかし、どちらもバッファオーバーラン以外の問題を抱えており、C言語における文字列への代入の面倒を解消するに至っていません。
こんな感じで、C言語では文字列への代入を行うだけでも命がけなわけですね。こう考えると、代入が楽々行えるstringオブジェクトはとても魅力的です。
まとめ
C言語の不便さが解れば、C++のありがたみも分かるというお話でした。もちろん、C言語の文字列もあらかじめ適切な長さが分かっていれば、メモリを無駄遣いせずにすむという利点もあります。また、文字列と言う名の配列なので一文字ずつのアクセスは別格に速いです。というか、C++のstringオブジェクトってC言語の文字列をメンバに持っていて、いろんな便利機能をメンバ関数とかoperatorとかを使って拡張したものなんですよね。だから、本質的にあまり違いはないんですよ。使い分けるとしたら、その文字列に行う操作が明確な場合はC言語の文字列を、文字列に行う操作が明確でない場合はstringオブジェクトをといった感じだと思います。私は単純にC言語のstring.hの関数は使うのが怖いので、stringオブジェクト派ですね。
追記(2019/8/10)
文字列の代入だったら、文字型のポインタを使えばいいと言うご指摘を受けました。
const char* str;
str = "aaaa";
printf("%s",str);
こうすれば、代入も難なく行えます。また、バッファオーバーランの問題もありません。
ただ、この書き方だと、文字列リテラルが配置される領域は静的領域に確保されるので一文字だけの変更をしようとしても、メモリアクセスエラーとなってしまうなどの欠点もありますね。(文字の変更をしようと思ったら、文字型配列を使わないといけないジレンマが・・・)
char *str; //constがないことに注意、警告が出ます
str = "aaaaa";
str[3] = 'b';//Thread 1: EXC_BAD_ACCESS
printf("%s",str);
まあ、色々書きましたけど、C言語の文字列は難しいんですね(静的領域に確保されるかスタック領域に確保されるかとか)。その点、C++のstirngオブジェクトはもちろん問題点もありますけど、使いやすくて便利なんですよね。利点はこれに尽きるかもしれません。メモリと速度の観点でいったらC言語の文字列を、柔軟に文字列の操作を行いたい時はstringオブジェクトをといった使いわけかな・・・