組み込みC/C++

C/C++リテラシー向上のためのページ

”長さ”について

文字列やバイナリデータを扱う上で”長さ”は非常に大切な要素です。しかしながらなかなか長さについて言及される事がないように思いましたので、ここで”長さ”について改めて考えてみます。

 たまに”長さ”なのに0スタートで数えるプログラム(0x00~0x0Fの長さを15と表現している場合0から数え上げてる)を書いている人がいますが混乱するのでやめるべきでしょう。1を足す必要があったりなかったり毎回考えてしまいますが、

・長さ、大きさの類は 1スタート

・アドレス、配列は 0スタート

と覚えておくとよいと思います。

 

長さといってすぐ思い浮かぶのは下記の演算子もしくは標準関数です。

sizeof() <-関数ではなく演算子、型や配列の大きさを知ることが出来る。

strlen()  <-cstring, string.h, stringで定義されている標準関数 文字列のbyte数を返す

mblen() <-cstdlib, stdlib.hで定義されている標準関数 文字列の文字数を返す

length() <- string 型のメソッド string型で定義された文字列のbyte数を返す

size() <- string 型のメソッド string型で定義された文字列のbyte数を返す

全部1スタートです。(当たり前ですけど)  試験用のコードと結果を下記に示します。

 

void Function(void){

char s1[]="abcde"; //配列への文字列代入(シンタックスシュガー) 勝手に終端文字入れる
char s2[6]="abcde";
char s3[6]={'a','b','c','d','e','\0'}; //素直に代入した場合 ここまですべて同じ意味
char s4[6]={'a'}; //s4[1]以降はNULL
char *s5 = "abcde"; //pointerへの文字列代入(シンタックスシュガー)
char s6[6]={'a','b','c','d','e','f'}; //NULL終端なし

std::string ss1 = s1;
std::string ss2 = s2;
std::string ss3 = s3;
std::string ss4 = s4;
std::string ss5 = s5;
std::string ss6 = s6;

std::cout << s1 << " sizeof->" <<  sizeof(s1) << " strlen->" << std::strlen(s1) <<  " length->" << ss1.length() <<  " size->" << ss1.size() << std::endl;
std::cout << s2 << " sizeof->" <<  sizeof(s2) << " strlen->" << std::strlen(s2) <<  " length->" << ss2.length() <<  " size->" << ss2.size() << std::endl;
std::cout << s3 << " sizeof->" <<  sizeof(s3) << " strlen->" << std::strlen(s3) <<  " length->" << ss3.length() <<  " size->" << ss3.size() << std::endl;
std::cout << s4 << " sizeof->" <<  sizeof(s4) << " strlen->" << std::strlen(s4) <<  " length->" << ss4.length() <<  " size->" << ss4.size() << std::endl;
std::cout << s5 << " sizeof->" <<  sizeof(s5) << " strlen->" << std::strlen(s5) <<  " length->" << ss5.length() <<  " size->" << ss5.size() << std::endl;
std::cout << s6 << " sizeof->" <<  sizeof(s6) << " strlen->" << std::strlen(s6) <<  " length->" << ss6.length() <<  " size->" << ss6.size() << std::endl;

//  std::cout << s1[6]; //<- 不定値
// std::cout << ss1[5]; //<- アクセスできない 代入時にlength情報を計算しNULL文字にはアクセスできないように制限をかけてる様子

s1[2] = '\0';
ss1[2] = '\0';

std::cout << "char->" << s1 <<  " sizeof->" <<  sizeof(s1) << " strlen->" << std::strlen(s1) << std::endl; //NULL文字を終端として扱う
std::cout << s1[0] << s1[1] << s1[2] << s1[3] << s1[4] << s1[5] << std::endl ;
std::cout << "string->" << ss1 <<  " sizeof->" <<  sizeof(ss1) << " length->" << ss1.length() <<  " size->" << ss1.size() << std::endl; //NULL文字が終端とは限らない
std::cout << ss1[0] << ss1[1] << ss1[2] << ss1[3] << ss1[4]  << std::endl ;

ss1 = "あいうえお";
std::cout << "string->" << ss1 <<  " sizeof->" <<  sizeof(ss1) << " length->" << ss1.length() <<  " size->" << ss1.size() << std::endl; 
}

 

のコードの出力結果は下記

 

abcde sizeof->6 strlen->5 length->5 size->5
/*sizeofは配列の大きさ、その他はNULL文字を除外している。 */
abcde sizeof->6 strlen->5 length->5 size->5
abcde sizeof->6 strlen->5 length->5 size->5
a sizeof->6 strlen->1 length->1 size->1
abcde sizeof->4 strlen->5 length->5 size->5
/*ポインタと配列の違いに注目、ポインタに大きさの情報はない。 */
abcdefフフフフフフフフフフ|ィ. sizeof->6 strlen->19 length->19 size->19
/*NULL文字が終端でない場合はNULL文字を探してはみ出してしまう。 */
char->ab sizeof->6 strlen->2
/*NULL文字を間に挟むと縮む */
ab de
string->ab de sizeof->32 length->5 size->5
/* NULL文字を間に挟んでも縮まない */
ab de string->あいうえお
sizeof->32 length->10 size->10
/*lengthもsizeもまったく同じ結果*/

 

 

まとめ

一般的に文字列長を得る標準関数はNULL文字を含まないのでバイナリとして扱いたい場合は得た文字列長に1を足す必要があるのが判ります(配列に文字列を入れる場合のサイズはlengthで得られる長さに+1をする事)。なおバイナリと文字列の分かれ目はNULL文字があるかないかです。文字列を扱っているのに終端をNULL文字にしていないシステムは見直した方が良いでしょう。

strlenとstring型のlengthも挙動が違いました。string型の方がよりバイナリデータを扱うことを意識した作りなのだと思います。

ところでlengthとsizeなぜ二種類あるのかが判りませんでした。

 

テーマとは外れますがstrlen()の型はsize_tとなっています。これは処理系のRAMマップが何ビット表現であるかで決まります。文字列は基本的にRAMに格納されるわけですから、RAMの最大値を型として持っているわけですね。