久々にCで面白いバグ(読者への挑戦状篇)
常日頃、面白いバグが無いかと探しています。見つけた時は嬉しくて、このアリエルエリアで紹介しています。
自分にとって面白いバグの条件は次のようになります。
- 一瞬気づかないが、指摘されると、あっ、と思えるもの
- コンパイラなど静的チェックでは見つからないもの
- どこでも起こりうるもの
普遍性と意外性を備えたバグが、好みのバグです。単純なのに意外、という性質を備えていれば更に良しですが、理想的なバグはそうそう転がっていません。最近の不満は、社内で、Javaのコードインスペクションをしているのに、面白いバグを発見できないことです。社員が優秀すぎるのでしょうか。わざと見つけにくいバグを仕込むぐらいの男気が欲しいものです。
Javaではなく、Cで久々に面白いバグに出会いました。
こんなコードです(少し変えています)。
// buggy code *(const char**)apr_array_push(arr) = arr->nelts == 0 ? "foo" : "bar";
背景となる説明(特にAPRのapr_array_push()の説明)がそれなりに必要です。しかし、バグの原因自体は、普遍性があります。
apr_array_push()とデータ型は次のようなコードです。説明に必要な部分だけを抜粋および少し改変してます。
struct apr_array_header_t { /** The amount of memory allocated for each element of the array */ int elt_size; /** The number of active elements in the array */ int nelts; /** The elements in the array */ char *elts; }; APR_DECLARE(void *) apr_array_push(apr_array_header_t *arr) { //このメモリ確保のコードは改変しています arr->elts = apr_palloc(arr->pool, arr->elt_size * new_size); // don't care new_size ++arr->nelts; return arr->elts + (arr->elt_size * (arr->nelts - 1)); }
正直、非常に分かりづらいコードです。提供している機能は動的配列です。例えば文字列の配列なら次のイディオムで使えます(このAPIの是非は今日の主眼ではありません)。このイディオムだけ認識しておけば、内部実装にはあまり深入りしなくても大丈夫です。
apr_array_header_t* arr = apr_array_make(mp, 32, sizeof(const char*)); *(const char**)apr_array_push(arr) = "foo"; *(const char**)apr_array_push(arr) = "bar"; *(const char**)apr_array_push(arr) = "baz";
buggy codeは実際にはループの中にありました。3項演算子で参照しているarr->neltsは、動的配列の要素数です。つまり、コードの意図は配列の先頭にだけ"foo"を追加して、残りは"bar"を追加するというものです(実際のコードでは、配列に追加する値は違うものです)。
ここまでで、最初に挙げたbuggy codeのバグの原因を推理できる全ての手がかりを提示しました。推理小説であれば、読者への挑戦状を書くところです。
せっかくなので、解答篇は明日にします。
ヒント
- ポインタ関連のバグではありません
- Javaでは(残念ながら)同じバグを起こせません
- K&R2の65ページ参照
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/c-bug/tbping