Checked Cは、C言語に境界チェック機能が追加されたものであり、より安全な形でCプログラミングを行えるようにするものだ。
Checked Cは、C言語(以下、単に「C」とする)に境界チェック機能が追加されたもの。マイクロソフトが開発し、オープンソースなプログラミング言語として公開されている。本稿執筆時点(2016年6月28日)では、その仕様書のバージョンは0.5となっている。
Cでは、ポインターと配列を同一視できる(ポインターを使って配列=メモリ上の連続した領域をアクセスできる)場面がある。このことは、プログラミングを容易にする面がある一方で、バッファオーバーランなどのエラーの種にもなる。Checked Cは配列やポインターでアクセス可能な範囲(境界)を定め、境界チェックを行うことにより、より安全にプログラミングを行えるようにするものだ。
また、Checked Cでは、既存のCコードはその意味を変えることがなく、そのまま実行できることを目指しており、従来のCコードとChecked Cコードを混在して記述できる。このようにすることで、CコードをChecked Cコードへと徐々に移行できるようにしている。
なお、本稿ではMac OS X上でChecked Cコンパイラをビルドして、可能なものについてはコードをコンパイルしている(ただし、本稿執筆時点ではChecked Cの仕様で記述されている多くの仕様が実装されていないので、お試し程度のものでしかない)。
Checked Cでは、既存のCコードに対して境界チェックを行うのではなく、新たなポインター型/配列型、構文を導入している。新たに導入された型は以下のものだ(「<T>」はC++やC#でおなじみの型パラメーターである)。これらをchecked型と呼び、従来のCの型をunchecked型と呼ぶ。
例として以下のような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;
}
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;
}
もちろん、通常はこんな間違いをするはずはない。が、プログラムコード的にはこれは誤ったコードではなくコンパイル可能であり、実行すればエラーが発生する。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);
^
「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;
}
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では従来のCの型(unchecked型)とChecked Cの型(checked型)を混在して記述できる。だが、プログラム中に全ての変数がchecked型であった方がよい部分があった場合にはcheckedブロックやchecked関数を使用できる。checkedブロックとchecked関数の内部ではポインターと配列に関してはchecked型のものだけしか使えない(現在のコンパイラでは未サポート)。
checked {
…… 省略 ……
}
checked int foo() {
…… 省略 ……
}
逆にcheckedブロックやchecked関数内でどうしてもunchecked型のポインターや配列を使う必要がある場合にはuncheckedブロックを作成し、その内部でのみそれらの型を使用する。
この他にもプログラマーが動的に境界チェックを行うコードを含めることも可能なようだ。これにはdynamic_check関数を使用する。この関数は引数を評価して、それが偽値であれば、ランタイムエラーを発生する。assertと似ているが、dynamic_check関数はデバッグ用のコードではなく、プロダクションコードに含めて、プログラム実行時に境界チェックを行うのが目的であることが異なる点だ(仕様には例と共に「dynamic_checkによりコードの挙動が変化するので、コンパイラはこの動的なチェックを導入することはないだろう」といったことが述べられているのが気になるところだ。この辺りの詳細は仕様が詰められていけばハッキリとするだろう)。
Checked Cは、C言語(以下、単に「C」とする)に境界チェック機能が追加されたものであり、より安全な形でCプログラミングを行えるようにするものだ。同時にCコードの漸次的なChecked Cコードへの移行も可能となっている。ただし、仕様自体のバージョンもまだ0.5であり、コンパイラでの仕様の実装の状況もまだまだだ。これからの進化に期待しよう。
Copyright© Digital Advantage Corp. All Rights Reserved.