第2回 自分が新人の頃に携わったシステムであまり見ないであろうバグがあったため紹介します。

とある通信システムの中継装置としての役割を果たすプログラムの開発でした。機能の一つとして通信内容をバイナリファイルに保存しているのですが、それを人間がわかる形にして表示するシステムのリファクタリング(コード整理)作業をしていました。バイナリファイルと同じ大きさの配列を用意して読み込み、その後はポインタを操作して内容を標準出力するのですが、使われていない変数(int a)を削除したら実行時にエラーとなりました。

簡単に書くと以下のようなプログラムだったかと思います。

  • int a; // 宣言のみでそれ以降未使用
  • FILE* fp; // ファイルポインタ宣言
  • char buf[バイナリファイルサイズ]; // バイナリサイズの配列宣言
  • fp = fopen("バイナリファイル", "rb"); // バイナリファイルを開く
  • fread(buf, sizeof(バイナリファイル), 1, fp); // バイナリファイル読み込み
  • printf("データは%d\n", *(int*)buf); // 通信データ表示
  • buf += sizeof(int); // ポインタ加算
  • printf("データ2は%c\n", *buf); // 通信データ表示
  • buf += sizeof(char); // ポインタ加算

使われていない変数を消しただけなので問題ないはずですが、コンパイルは通り、実行時にエラーとなりました。

何でだと思いますか?

ポインタはC,C++,C#等にある機能で知らない人もいるかと思うので簡単に説明します。通常、変数を宣言したときに、コンピュータ上の空いているメモリに割り当てられます。char a[8];と宣言すると図のようにどこかのメモリ空間に8byte確保します。

どこかのメモリ空間に変数が存在するのですが、そのメモリの場所のことをポインタといいます。たとえばaの場所が300番地から8byte確保されているとしたら、300番から307番までのメモリを占有します。

ポインタを使った例が以下になります。

  • char a[8]; // 配列aを宣言(ここでコンピュータ上のメモリに8byteの領域が確保される)
  • char *p; // ポインタ変数を宣言
  • strcpy(a, "abcdefg"); // aにabcdefgを格納
  • p = a; // 変数aの確保されたメモリの番地を格納
  • printf("%c", a[0]); // 文字aが出力
  • printf("%c", a[2]); // 文字cが出力
  • printf("%c", *p); // 文字aが出力
  • p++; // ポインタの位置をひとつ加算
  • printf("%c", *p); // 文字bが出力
  • p = p + 2; // ポインタの位置を2加算
  • printf("%c", *p); // 文字dが出力

ポインタの説明は簡単ですがここで終わりにします。

なぜ、使われていない変数を消したら実行時エラーとなったかのネタバラシをします。実はバイナリファイルを格納するバッファのサイズが4byte分足りていませんでした。バイナリファイルのサイズがたとえば30004だとしたらバッファのサイズが4バイト不足の30000だったのです。

通常バイナリファイルを読み込んだところで(fread関数)エラーとなります。しかしたまたま使われていない変数int aの確保されたメモリの位置がバイナリファイル格納用配列の隣に確保されていたみたいです。

おそらく当初は30000byteだったのでしょうが、仕様変更等によりサイズが変わったのでしょう。問題なく動いてしまうため、前任者も気づかなかったのではないかと思います。

このバグは個人単位ではたぶん・・・見る機会の無いレアなケースだと思ったので紹介しました。

仮想化セキュリティの決定版 CS-TWiSt

Page Top

Copyright © 2019 (株)テイルウィンドシステム All rights Reserved.