ヌル終端文字列

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

プログラミングにおいて、ヌル終端文字列(ヌルしゅうたんもじれつ、英語: null-terminated string)とは、文字配列に格納し、ヌル文字'\0'ASCIIコードではNUL)でその終端を表した文字列である。C言語等で用いられることからC文字列(C string)とも言い、ASCIIコードの後にゼロ(zero)があることからASCIIZとも呼ばれる[1]

ヌル終端文字列の長さは、文字列の先頭から見て最初のヌル文字を発見することでしかわからない。その計算量は文字列長に比列する(O(n))。また、ヌル文字そのものは文字列に含めることはできず、ヌル文字は終端に1つだけ存在する。

歴史[編集]

ヌル終端文字列は、PDP-11アセンブリ言語.ASCIZディレクティブ、および、PDP-10のマクロアセンブリ言語であるMACRO-10英語版ASCIZディレクティブとして導入された。これらはC言語の開発に先行するものであるが、その後は他の形が文字列がよく使われた。

C言語(およびそれから派生した言語)の開発において、メモリは非常に限られたものだったため、文字列長を保存するのにオーバーヘッドが1バイトだけで済むのは魅力的であった。その当時よく使われていたのは「Pascal文字列」で(初期のMicrosoft BASICでも使われていた)、これは、文字列長を先頭に数値で格納していた。この方式ならばヌル文字を文字列に含めることが可能であり、また、文字列長を求めるのが1回のメモリアクセスだけで済む(計算量が O(1) の定数時間になる)。しかし、C言語の開発者であるデニス・リッチーは、既にBCPLで確立していたヌル終端を選択した。これは、文字列のカウントを8ビットまたは9ビットのスロットに格納することで文字列長が制限されるのを避けるためと、カウントを維持する方法は終端を用いる方法よりも、彼の経験上使いやすくなかったためである[2]

このC言語の設計は、CPUの命令セットの設計に影響を与えた。1970年代から1980年代にかけてのいくつかのCPU(例えばザイログZ80DECVAX)は、文字列長が前に置かれた文字列を取り扱うための命令が存在した。しかし、ヌル終端文字列が主流となったことにより、"Logical String Assist"命令をIBM ES/9000 520に加えるという1992年のIBMの決定に見られるように、CPU設計者はヌル終端文字列を考慮に入れるようになった。

FreeBSDの開発者ポール=ヘニング・カンプ英語版は『ACM Queue英語版』の中で、2バイト(1バイトではない)の文字列長の使用に対するC文字列の勝利を「最も高価な1バイトの間違い(the most expensive one-byte mistake)」と言及している[3]

実装[編集]

C言語はヌル終端文字列を基本の文字列型として実装している[4]標準Cライブラリには、ヌル終端文字列を扱うための以下のような多くの関数がある。

  • 文字列の長さを求める
  • 文字列を他の文字列にコピーする
  • 文字列を他の文字列に加える(結合する)
  • 指定の文字が文字列中で最初(または最後)に登場する箇所を見付ける
  • 文字列中で指定の文字群を含む(または含まない)最初の箇所を見付ける
  • 指定の文字列が文字列中で最初に登場する箇所を見付ける
  • 2つの文字列を辞書順で比較する
  • 文字列を分割する
  • 数値や文字列を出力可能な形式に変換する
  • 文字列を数値に変換する
  • 1バイト文字の文字列をワイド文字の文字列に変換する
  • 1バイト文字やワイド文字とマルチバイト文字の文字列を相互に変換する

制限[編集]

実装が単純であるために、この表現にはエラーとパフォーマンス問題の傾向がある。

ヌル終端文字列は歴史的にコンピュータセキュリティ上の問題を作ってきた[5]。文字列を宣言するときにヌル文字のための領域を割り当て忘れると、最大の長さの文字列を格納したときにヌル文字が隣接したメモリ領域に書かれてしまう。ヌル文字を格納し忘れるのもバグの原因となる。プログラムのテスト時に、以前そのメモリ領域を使った時のヌル文字が偶然残っていると、そのバグを見つけられないことがある。文字列を固定サイズのバッファへコピーする際に、多くのプログラムではバッファのサイズを気にしていない。そして、コピーする文字列がバッファサイズより長いとバッファオーバーランを引き起こす。

文字列にヌル文字('\0')を格納できないので、文字列データとバイナリデータは明確に分けておき、それぞれ異なる関数で取り扱う必要がある。

文字列長を求める際の速度の問題は、他の計算量 O(n) の操作と組み合わせて使用することで軽減される。strlcpyはそのようになっている。

文字のエンコード[編集]

ヌル終端文字列では、全て0のバイトをどこにも含まないエンコードが必要である。

全て0のバイトはヌル文字として扱われるので、ヌル終端文字列にASCIIUTF-8の文字列をそのまま含むことはできない[6][7][8]。そこで、ヌル文字を含まないASCIIUTF-8のサブセットを使用するのが一般的である。いくつかのシステムでは"modified UTF-8"を使用している。これは、ヌル文字を2つの0でないバイト(0xC0, 0x80)で表現し、ヌル終端文字列に格納できるようにしたものである。これはセキュリティ上のリスクがあるため標準のUTF-8の規格外である。C0 80 NUL はセキュリティ確認では文字列終端として、実際の使用時は文字としてみなされるかもしれない。

UTF-16は2バイトの整数値を使用し、どちらかのバイトが全て0になり得るので、ヌル終端文字列に格納することができない。しかし、いくつかの言語では、16ビットのヌル文字で終端することでUTF-16を使用した文字列を実装している。この場合、シングルバイト(8ビット)のヌル文字を想定している関数は使用することができない。

発展[編集]

C文字列の処理の誤りを少くするために、多くの試みがなされた。その一つの方法が、標準Cライブラリgetsのような危険な関数を廃止するために導入された、より安全で使いやすいstrdupstrlcpyなどの関数の追加である。他に、安全な呼び出ししか行われないように、C文字列にオブジェクト指向のラッパーを追加する方法もある。

最新のシステムではメモリの使用は懸念がより少ないので、多バイトの文字列長も許容された。文字列長のために使用されるメモリが懸念されるような多くの小さな文字列が多数ある場合でも、ハッシュテーブルを使用することでより少ないメモリで管理できるようになっている。C文字列の後継者は、32ビットやそれ以上の文字列長の値を持っている。例えばC++Standard Template Library(STL)のstd::stringQtQStringMicrosoft Foundation Class(MFC)のCStringCore FoundationCFStringFoundation Kit英語版NSStringなどである。このような、文字列を格納するためのより複雑な構造を、string(ひも)に対してropeロープ英語版)と言う。

出典[編集]

  1. ^ 文字はASCIIだけに限らないことに注意
  2. ^ Dennis M. Ritchie (1993). [The development of the C language]. Proc. 2nd History of Programming Languages Conf.
  3. ^ Kamp, Poul-Henning (25 July 2011), “The Most Expensive One-byte Mistake”, ACM Queue 9 (7), ISSN 1542-7730, http://queue.acm.org/detail.cfm?id=2010365 2011年8月2日閲覧。 
  4. ^ Richie, Dennis (2003年). “The Development of the C Language”. 2011年11月9日閲覧。
  5. ^ Rain Forest Puppy (9 September 1999). “Perl CGI problems”. Phrack Magazine (artofhacking.com) 9 (55): 7. http://artofhacking.com/files/phrack/phrack55/P55-07.TXT 2012年1月6日閲覧。. 
  6. ^ UTF-8, a transformation format of ISO 10646”. 2013年9月19日閲覧。
  7. ^ Unicode/UTF-8-character table”. 2013年9月13日閲覧。
  8. ^ Kuhn, Markus. “UTF-8 and Unicode FAQ”. 2013年9月13日閲覧。