Checked C(プログラミング言語)Dev Basics/Keyword

Checked Cは、C言語に境界チェック機能が追加されたものであり、より安全な形でCプログラミングを行えるようにするものだ。

» 2016年06月29日 05時00分 公開
[かわさきしんじInsider.NET編集部]
「Dev Basics/Keyword」のインデックス

連載目次

Checked Cとは

 Checked Cは、C言語(以下、単に「C」とする)に境界チェック機能が追加されたもの。マイクロソフトが開発し、オープンソースなプログラミング言語として公開されている。本稿執筆時点(2016年6月28日)では、その仕様書のバージョンは0.5となっている。

マイクロソフトのChecked Cページ マイクロソフトのChecked Cページ

 Cでは、ポインターと配列を同一視できる(ポインターを使って配列=メモリ上の連続した領域をアクセスできる)場面がある。このことは、プログラミングを容易にする面がある一方で、バッファオーバーランなどのエラーの種にもなる。Checked Cは配列やポインターでアクセス可能な範囲(境界)を定め、境界チェックを行うことにより、より安全にプログラミングを行えるようにするものだ。

 また、Checked Cでは、既存のCコードはその意味を変えることがなく、そのまま実行できることを目指しており、従来のCコードとChecked Cコードを混在して記述できる。このようにすることで、CコードをChecked Cコードへと徐々に移行できるようにしている。

 なお、本稿ではMac OS X上でChecked Cコンパイラをビルドして、可能なものについてはコードをコンパイルしている(ただし、本稿執筆時点ではChecked Cの仕様で記述されている多くの仕様が実装されていないので、お試し程度のものでしかない)。

checked型

 Checked Cでは、既存のCコードに対して境界チェックを行うのではなく、新たなポインター型/配列型、構文を導入している。新たに導入された型は以下のものだ(「<T>」はC++やC#でおなじみの型パラメーターである)。これらをchecked型と呼び、従来のCの型をunchecked型と呼ぶ。

  • ptr<T>型: T型のオブジェクトを指すポインター。ただし、ptr<T>型のポインターに対しては算術演算を行えない。つまり、単一のメモリ領域を指すポインターである
  • array_ptr<T>型: T型の値を要素とする配列の要素を指すポインター。以下のchecked配列と組み合わせて使用する。要素へのアクセス時には境界チェックが必要に応じて行われる
  • span<T>型: array_ptr<T>型と似ているが、span<T>型では範囲の下限と上限(境界情報)、現在値といった情報が保持される
  • checked配列: 「T array checked[要素数]」の形式で宣言される配列。配列の境界チェックが行われるようになる(多次元配列も可)

 例として以下のようなCコードをChecked Cコードに書き換えることを考えてみよう(size_tを使っていないなど手抜きなところがあることはご容赦願いたい)。

#include <stdio.h>

void set_data(int *p, int count) {
  for (int i = 0; i < count; i++) {
    *(p++) = i;
  }
}

int main() {
  int array[10];
  set_data(array, 10);
  for(int i = 0; i < 10; i++) {
    printf("%d ", array[i]);
  }
  printf("\n");
  return 0;
}


従来のCコード

 set_data関数はpパラメーターにintポインターを、countパラメーターにint型の値を受け取る。このコードはpパラメーターが指すのが配列であり、そのサイズがcountパラメーターで指定される値よりも大きいことを前提としている。呼び出し側はまさにその通りのデータを用意して、set_data関数を呼び出している。だが、main関数の中では以下のような呼び出しもできてしまう。

int main() {
  int i = 100;
  int *p = &i;
  set_data(p, 10);
  printf("%d\n", *p);
  return 0;
}


誤ったset_data関数呼び出し

 もちろん、通常はこんな間違いをするはずはない。が、プログラムコード的にはこれは誤ったコードではなくコンパイル可能であり、実行すればエラーが発生する。Checked Cでは、このような誤った呼び出しが行えないようにできる。これにはコードを以下のようにする。

void set_data(array_ptr<int> p, int count) {
  …… 省略 ……
}

int main() {
  int i = 100;
  ptr<int> p = &i;
  set_data(p, 10);
  printf("%d\n", *p);
  return 0;
}


修正後のコード

 set_data関数のpパラメーターはint型の配列要素を指すポインターであることと、main関数の変数pはint型の単一のオブジェクトを指すポインターであることが明記されている。このコードをコンパイルすると、例えば以下のようなエラーが発生する。

$ clang -fcheckedc-extension checkedcode.c 
checkedcode.c:10:12: error: passing 'ptr<int>' to parameter of incompatible type
      'array_ptr<int>'
  set_data(p, 10);
           ^


Checked Cでは上のコードはコンパイルエラーとなる

 「ptr<int>型の値をarray_ptr<int>型には変換できない」というメッセージとともにコンパイルの時点でエラーとなる。このように、Checked Cは静的なエラーチェックも行われる。仕様からは、配列要素へのアクセス時には動的に境界チェックが行われるとも思われるが、本稿執筆時点で配布されているChecked Cコンパイラ(ソースからビルドしたもの)にはそこまでの機能は実装されていないようだ。

 最初のコードをChecked Cコードに修正すると次のようになる。

void set_data(array_ptr<int> p, int count) {
  …… 省略 ……
}

int main() {
  int array checked[10];
  set_data(array, 10);
  …… 省略 ……
  return 0;
}


最初のコードをChecked Cコードに修正したもの

 main関数では配列をchecked配列にしている(set_data関数は上でも見たので説明は省略)。これにはキーワード「checked」を配列であることを示す「[]」の前に置くだけでよい。

 従来のCでは「T型の配列は、T型を指すポインターに暗黙的に変換できる」ように、Checked Cでは「T型のchecked配列は、T型を指すarray_ptrに暗黙的に変換できる」。そのため、上のコードではchecked配列である変数arrayをset_data関数に問題なく渡せている。

 仕様書を見ると、これらの型の変数にはその境界情報を付加することも可能となっている。これにはコロン(:)に続けて、count/boundsなどのキーワードと共に要素数または範囲を指定する。仕様書からの例を以下に示す(現在のコンパイラでは未サポート)。

int find(int key, array_ptr<int> a : count(len), int len)
{
  …… 省略 ……
}

int sum(array_ptr<int> start : bounds(start, end), array_ptr<int> end)
{
  int result = 0;
  array_ptr<int> current : bounds(start, end) = start;
  …… 省略 ……
}


境界情報の付加
コードはChecked Cの仕様のバージョン0.5から引用したもの。
find関数ではcountキーワードと共に配列の要素を指定している。sum関数では境界の下限と上限を、boundsキーワードを使用して指定している。

その他の新機能

 冒頭で述べたように、Checked Cでは従来のCの型(unchecked型)とChecked Cの型(checked型)を混在して記述できる。だが、プログラム中に全ての変数がchecked型であった方がよい部分があった場合にはcheckedブロックやchecked関数を使用できる。checkedブロックとchecked関数の内部ではポインターと配列に関してはchecked型のものだけしか使えない(現在のコンパイラでは未サポート)。

checked {
  …… 省略 ……
}

checked int foo() {
  …… 省略 ……
}


checkedブロックとchecked関数

 逆にcheckedブロックやchecked関数内でどうしてもunchecked型のポインターや配列を使う必要がある場合にはuncheckedブロックを作成し、その内部でのみそれらの型を使用する。

 この他にもプログラマーが動的に境界チェックを行うコードを含めることも可能なようだ。これにはdynamic_check関数を使用する。この関数は引数を評価して、それが偽値であれば、ランタイムエラーを発生する。assertと似ているが、dynamic_check関数はデバッグ用のコードではなく、プロダクションコードに含めて、プログラム実行時に境界チェックを行うのが目的であることが異なる点だ(仕様には例と共に「dynamic_checkによりコードの挙動が変化するので、コンパイラはこの動的なチェックを導入することはないだろう」といったことが述べられているのが気になるところだ。この辺りの詳細は仕様が詰められていけばハッキリとするだろう)。


 Checked Cは、C言語(以下、単に「C」とする)に境界チェック機能が追加されたものであり、より安全な形でCプログラミングを行えるようにするものだ。同時にCコードの漸次的なChecked Cコードへの移行も可能となっている。ただし、仕様自体のバージョンもまだ0.5であり、コンパイラでの仕様の実装の状況もまだまだだ。これからの進化に期待しよう。

参考資料

  • Checked C: マイクロソフト内のChecked Cページ
  • Checked C: GitHub上のChecked C仕様リポジトリ

「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。