Home / ぼやきごと / 2011-06-30
2011-06-30

C++ string クラスの size や length は strlen とは違う

C/C++で文字列を char の配列で持つ場合、特定の位置以降を切り捨てる方法の1つとして、 '\0' を代入するという方法があります。

ソースコード
すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
#include <iostream>
#include <cstring>
 
int main(int, char**)
{
    using namespace std;
 
    char text[] = "ABCDEFGH";
 
    cout << "[Before]" << endl;
    cout << "text         : " << text << endl;
    cout << "strlen(text) : " << std::strlen(text) << endl;
 
    text[4] = '\0';
 
    cout << "[After]" << endl;
    cout << "text         : " << text << endl;
    cout << "strlen(text) : " << std::strlen(text) << endl;
 
    return 0;
}
実行結果(VC++ 2010 および gcc 4.3.0)
  1
  2
  3
  4
  5
  6
[Before]
text         : ABCDEFGH
strlen(text) : 8
[After]
text         : ABCD
strlen(text) : 4

ですが、同じことを std::string でやろうとすると、 size メンバ関数や length メンバ関数の挙動が違うことに気付くと思います。

ソースコード
すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
#include <iostream>
#include <string>
 
int main(int, char**)
{
    using namespace std;
 
    std::string text = "ABCDEFGH";
 
    cout << "[Before]" << endl;
    cout << "text          : " << text << endl;
    cout << "text.size()   : " << text.size() << endl;
    cout << "text.length() : " << text.length() << endl;
 
    text[4] = '\0';
 
    cout << "[After]" << endl;
    cout << "text          : " << text << endl;
    cout << "text.size()   : " << text.size() << endl;
    cout << "text.length() : " << text.length() << endl;
 
    return 0;
}
実行結果(VC++ 2010)
  1
  2
  3
  4
  5
  6
  7
  8
[Before]
text          : ABCDEFGH
text.size()   : 8
text.length() : 8
[After]
text          : ABCD FGH
text.size()   : 8
text.length() : 8
実行結果(gcc 4.3.0)
  1
  2
  3
  4
  5
  6
  7
  8
[Before]
text          : ABCDEFGH
text.size()   : 8
text.length() : 8
[After]
text          : ABCDFGH
text.size()   : 8
text.length() : 8

なお、 c_str メンバ関数や data メンバ関数で取得できる文字列は "ABCD" になります。

この結果から次のことがわかると思います。

  • std::string 内部において '\0' は単に要素としてとりうる値の1つに過ぎない。
  • size メンバ関数や length メンバ関数は内部でアサインされている要素の数を返す。
    • strlen 関数のように '\0' の前までの文字数を返すわけではない。
  • std::ostream<< 演算子の std::string 用オーバロードは size メンバ関数が返すサイズ値を基に文字列を出力する。*1

ただし、 const char* を引数に取るコンストラクタや = 演算子のオーバロード等では最初の '\0' の前までを取り出して内部に設定してくれます。
これは単にそれらのメンバ関数がそういう実装になっているというだけであり、 std::string 内部での持ち方が変わるわけではありません。

size メンバ関数や length メンバ関数の上述のような仕様は、特に std::ostringstream 等でバイナリデータを扱う場合に意味があります。
バイナリデータには当然値が 0 のデータも普通にあるわけで、 strlen 関数のような仕様では正しいデータサイズが取れなくなってしまいます。

普通に文字列データとして std::string を使っている分にはあまり気にする必要のないことですが、混同しないようにしましょう。

Category: [C++][プログラミング] - 2011-06-30 02:01:36

*1 元を辿れば basic_ostreambasic_string の話なんですが割愛。