はじめに 編集

標準テンプレートライブラリ (英: Standard Template Library、STL) は、言語を問わずあらゆる場面でよく用いられるデータ構造等を提供することを目的として導入されました。STLの中には、連想配列リストなどのデータ構造が定義されています。これらは、Perlや、Pythonなどの言語では標準のデータ構造として言語に組み込まれています。これらのデータ構造は、Cのプログラムでもよく用いられます。しかし、Cはこのようなデータ構造を言語自身の機能として持ってはいなかったため、Cを用いるプログラマは、プログラムを作成するときに、まず用いるデータ構造を作成する必要がありました。例えば、Apache httpd の Apache Portable Runtime や、glibでは、STLで定義されるようなデータ構造が作成されています。 STLは作業の重複を省く意味でも積極的に用いるのがよいでしょう。ただし、これらのデータ構造を用いることだけが目的なら、PerlPythonなどを試してみることを勧めます。これらの言語はC++ほど速く実行されはしません。しかし、C++と比べて比較的簡潔にこれらを用いることが出来ます。そのため、STLはC++を用いる上でこれらのデータ構造が必要になった時に使われます。

文字列 編集

CではNULL文字で終端されたchar型の配列 (以下、C形式の文字列) を用いる事で文字列を操作します。 以下の例を見てください。

例1:C形式の文字列を操作する
#include <stdio.h>
#include <string.h>

int main() {
  const char *s1 = "a string";
  char s2[256];

  strcpy(s2, s1); /* 文字列のコピー */
  printf("%s\n", s2);
  strcat(s2, s1); /* 文字列の追加 */
  printf("%s\n", s2);
  printf("%zu\n", strlen(s2)); /* 文字列のサイズ */
}

C++では、C形式の文字列の代わりにstd::string (以下、単にstring) クラスを用いて文字列操作を行うことができます。 stringクラスを使って上記のコードと同じ働きをするコードを書くと、次のようになります。

例2:例1と同じ動作をするコード
#include <iostream>
#include <string>

int main() {
  std::string s1 = "a string";
  std::string s2;

  s2 = s1; /* 文字列のコピー */
  std::cout << s2 << std::endl;
  s2 += s1; /* 文字列の追加 */
  std::cout << s2 << std::endl;
  std::cout << s2.size() << std::endl; /* 文字列のサイズ */
}

stringクラスにはC形式の文字列を使うのにくらべて、次のような違いがあります。

  • 事前に文字列の最大数を指定する必要がなく、適宜メモリサイズが拡張される。
  • コピーや追加等の操作を演算子により行える。
  • 現在の文字列のサイズを、オブジェクトの中に持っている。

ところで、例1を関数に分割すると、次のようになります。

例3:例1を関数に分割したバージョン
#include <stdio.h>
#include <string.h>

void func(const char *s1) {
  char s2[256]; //<--s1の文字列サイズチェックがないとs2にコピーあるいは追加する時にバッファオーバーランが発生する可能性がある

  strcpy(s2, s1); /* 文字列のコピー */
  printf("%s\n", s2);
  strcat(s2, s1); /* 文字列の追加 */
  printf("%s\n", s2);
  printf("%zu\n", strlen(s2)); /* 文字列のサイズ */
}

int main() {
  char s1[] = "a string";

  func(s1);
}

例3ではfunc()main()からしか呼ばれません。 しかし、func()に渡された文字列の長さをチェックしないと、s2に対して範囲外へのアクセスを行う可能性があるため危険です[注 1]

これに対してC++バージョンを関数分割すると、次のようになります。

例4:例2を関数に分割したバージョン
#include <iostream>
#include <string>

void func(const std::string &s1) {
  std::string s2;

  s2 = s1; /* 文字列のコピー */
  std::cout << s2 << std::endl;
  s2 += s1; /* 文字列の追加 */
  std::cout << s2 << std::endl;
  std::cout << s2.size() << std::endl; /* 文字列のサイズ */
}

int main() {
  std::string s1 = "a string";

  func(s1);
}

stringクラスは必要に応じて内部のバッファが拡張されるので、引数で渡された文字列のサイズを気にせずコピーや追加を行うことが出来ます。

C11の可変長配列を使った対策 編集

C11からは可変長配列がサポートされたので、必要な領域をスタック上に確保することができます。

例5:例1を可変長配列を使って対策したバージョン
#include <stdio.h>
#include <string.h>

void func(const char *s1) {
  const int len = strlen(s1);
  char s2[2 * len + 1]; //<--可変長配列を使い必要なバッファ長だけ領域を確保。

  strcpy(s2, s1); /* 文字列のコピー */
  printf("%s\n", s2);
  strcat(s2, s1); /* 文字列の追加 */
  printf("%s\n", s2);
  printf("%zu\n", strlen(s2)); /* 文字列のサイズ */
}

int main() {
  char s1[] = "a string";

  func(s1);
}
strlen の返す値には終端のNULL文字は含まれていないので + 1 していることに注意してください。

注釈 編集

  1. ^ 257文字以上の文字列をfunc()に渡すとバッファオーバーランを起こし関数の戻り番地が書き換えられるなどの原因となります