連載
» 2017年10月11日 10時00分 公開

山浦恒央の“くみこみ”な話(99):タダでソフト開発の生産性と品質を上げる方法(9):メモリリークを一瞬で見つける「Valgrind」(その2) (2/3)

[山浦恒央 東海大学 大学院 組込み技術研究科 非常勤講師(工学博士),MONOist]

2.動的解析と静的解析

 導入したいツールを探す場合、ツールの種類に着目するのが近道です。Valgrindの機能を解説する前に、プログラムを解析するツールの概要を簡単に説明します。プログラムの解析ツールには、「動的解析」と「静的解析」の2種類があります。

 動的解析は、プログラムを実際に動作させて解析するものです。テスト網羅度の計測やデバッガも、この一種ですね。例えば、第86回で紹介した「gcov」が動的解析ツールです。今回紹介するValgrindも、プログラムを実行して使うので動的解析ツールです。

 静的解析は、プログラムを動かさずに解析し、構造上の誤りを形式的に検出する手法です。静的解析は、コーディング規約やコードメトリクスを検出するタイプで、プログラムを動作させなくても解析できます。例えば、第91回で紹介した「SourceMonitor」は、プログラムを動作せずに解析するので、静的解析ツールです。

 解析ツールを探す際は、両者の違い、長所と短所を把握して調べるとよいでしょう。

3.Valgrindで検出できるバグ

 前回は、Valgrindの環境を構築する方法と、メモリリークを検出する具体例を解説しました。今回は、例題を示しながら、以下のバグを検出する方法を説明します。

  • 初期化していない変数
  • 領域外読み込みと書き込み

3.1 初期化していない変数

 「初期化していない変数」とは、その名の通り、宣言はしたが初期化しないまま使用している変数です。C言語では、ローカル変数を宣言するだけでは、値に何が入るか分かりません※4)。通常は「int val = 1」のように、初期化します(この記事の読者は、初期化しないことはないでしょうが)。例えばリスト1をご覧ください。

※4)大学でのプログラミング演習の授業では、学生の多くが「初期化していない変数」に戸惑うようです。例えば、「int a;」としたが「a」を初期化せず、「a」の値をコンソールに出力させると、意味不明な数値が画面に現れ、ビックリすることが少なくありません。場合によっては、「私のPCは壊れています」と言い出す生徒もいます。

#include <stdio.h>
main() {
	int x;
	print("%d",x);
}
リスト1 未初期化変数

 リスト1は、変数xを宣言し、コンソールに変数xを出力するプログラムです。簡単なプログラムですね。変数xは初期化しておらず、初期値は不定です。このプログラムに対して、解析をかけると図1のようなメッセージが出ます。

図1 図1 リスト1の実行結果

 図1は、リスト1のプログラムの解析結果の一部です。1行目に、「Conditional jump or move depends on uninitialised value(s)」という記述があります。これは、未初期化の変数がある場合に出るメッセージで、「初期化していない変数の値により、条件ジャンプや処理をしようとしている」の意味です。3行目を「int x=1;」に変更すると、このエラーメッセージは消えます。プロのエンジニアが変数を初期化せずに使用することは、まずありませんが、うっかりミスを防ぐため、Valgrindで自動チェックするといいでしょう。

3.2 領域外読み込み

 プログラミングの単純ミスとして、配列の領域外読み込みや書き込みも、よく見かけます。例えば、「int array[100]と宣言した後、array[100]を読んでしまった」は、王道的なバグでしょう。C言語でarray[100]と宣言すると、実際には、array[0]からarray[99]までしかアクセスできず、array[100]は領域外読み出しとなります。制御構文が複雑になると、見落とすので注意が必要です。Valgrindを使うと、間違って領域外の変数にアクセスした時に、メッセージを表示します。リスト2で考えてみましょう。

#include<stdio.h>
#include<stdlib.h>
main(){
	char *str;
	str = (char*)malloc(5);
	printf("%c",str[5]);
	free(str);
}
リスト2 領域外読み込みの例

 リスト2は、malloc関数で5バイト分の領域を確保し、その内容をコンソールに表示するものです(str[0〜4]は、未初期化となっていますが、無視してください)。printfで、配列の領域外のstr[5]を読み出しています。strは、0〜4までしかアクセスできませんので、領域外の読み込みですね。Valgrindを実行すると、以下のメッセージでバグが分かります。

図2 図2 リスト2の実行結果

 図2は、リスト2を実行時のログの一部を示したものです。「invalid read of size 1」と出ています。つまり、「1バイトを誤って読み込んでいる」という意味です。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.