CppcheckでC/C++コードの静的解析を行う

Cppcheckは、C/C++用のフリーの静的解析ツールです。静的解析とはプログラムを実行せず、ソースコードそのものを解析して誤りや異常の発生しそうな箇所を特定するというものになります。

Cppcheckは商用のものと比べると機能は大きく劣りますし、フォントを変えれなかったり警告メッセージが英語であったり、ソースコードの全角文字は化けることがあるなど不便なところも多くあります。しかしほぼ無設定で簡単に解析が行えるため、テスト前には必ず通すようにしています。

目次

必要なもの

Cppcheck

本体です。

Visual Studio Code

Visual Studio Codeである必要はないのですが、デフォルトで対応しているので推奨します。

設定

設定はいくつかありますが、本当に必要なものは1つだけです。使用するエディターを設定します。

デフォルト設定はメモ帳になっており、指摘箇所(行)へ飛ぶことができません。このためコマンドラインにより指定した行で開くことのできるエディターが必須となります。「Visual Studio Code」「Mery」「サクラエディタ」などを用意してください。

  • この設定は、Cppcheckをバージョンアップする毎に必要です。
STEP
メニュー [編集 > 設定] を開きます。
STEP
アプリケーションタブを開きます。
STEP
使用するエディターを設定します。

手順はVisual Studio Codeとそれ以外で操作が変わります。Visual Studio Codeの場合は表示されている名前を選択して、[デフォルトとして設定] ボタンをクリックするだけで済みます。それ以外のエディターを使用する場合は、[追加] ボタンをクリックして次へ進んでください。

STEP
[参照] ボタンをクリックしてエディターのexeへのパスを通します。
STEP
パラメータにコマンドラインを入力します。

こちらは使用するエディターによって入力方法が変わるので、頑張って調べてください。参考までに、いくつかエディターの例を示しておきます。
・EmEditor:(file) /l (line)
・Mery:/l (line) (file)
・サクラエディタ:(file) -Y=(line)

その他推奨設定

STEP
メニュー [編集 > 設定] を開きます。
STEP
[エラーIDを “Id” に表示する] にチェックを付けます。

これにより指摘リストの中でIDを表示するようになります。

使用方法

メニュー [チェック > ディレクトリ選択] にて、ソースコードを保存しているフォルダーを選択します。これだけで解析を開始し、異常を検知したファイルがあれば表示します。

  • ソースファイルのエンコードは「UTF-8 with BOM」でなければ全角文字が化けます。「UTF-8」ではいけないようです。ただし化けるだけで解析には影響しないと思います。

解析の結果指摘事項が見つかると、下図のような表示となります。
ここで①の指摘をクリックすると、②に指摘の概要と、③に実際の該当箇所が表示されます。また①をダブルクリックすることで、設定で指定したエディターにて指摘箇所のソースファイルを開きます。

指摘

以降にCppcheckが検出する指摘の例を紹介します。

見出しとなっている「uninitvar」などの文字列はCppcheckの示すエラーIDです。設定でエラーIDを表示するにチェックを付けると、表示されるようになります。また「CWE」はCommon Weakness Enumerationの略で、共通脆弱性タイプです。ソフトウェアやハードウェアの脆弱性をパターン化し、番号を割り振って識別したものとなります。CppcheckではエラーIDに該当するCWE-IDも表示します。

警告

invalidPrintfArgType_sint(CWE-686)

フォーマットの型指定が間違っています。型を揃えてください。

[不適合例]

int num = 0;
sprintf(buf, "%u", num);

[適合例]

unsigned int num = 0;
sprintf(buf, "%u", num);
oppositeInnerCondition(CWE-398)

デッドコードがあります。

[不適合]

if (a == 0) {
  if (a == 1) {
    // ここには来れない
  }
}
pointerSize(CWE-467)

ポインター変数に対してsizeof演算子を使ってはいけません。

[不適合]

void func(char str[])
{
  memset(str, 0x00, sizeof(str));
}

[適合例]

void func(char str[], int size)
{
  memset(str, 0x00, size);
}
uninitvar(CWE-457)

変数を初期化していません。値を初期化してから使用してください。

スタイル

clarifyCondition(CWE-398)

( )で囲み条件を明確にしてください。

[不適合例]

if (data & 0x01 != 0) {
}

[適合例]

if ((data & 0x01) != 0) {
}
constParameter(CWE-398)

関数内で書き換えないポインター変数はconst宣言すべきです。

[不適合例]

void func(char buf[], int len) {
  // 関数内でbufの値は書き換えない
}

[適合例]

void func(const char buf[], int len) {
  // 関数内でbufの値は書き換えない
}
redundantCondition(CWE-398)

条件が常に真です。

[不適合例]

if ((x == 0 ) && (x != 1)) {
}
redundantAssignment(CWE-563)

変数を使用する前に上書きしています。

unreadVariable(CWE-563)

変数に格納した値を使用していません。

unusedFunction(CWE-561)

関数を使用していません。未使用の関数は削除してください。

unusedVariable(CWE-563)

変数を使用していません。未使用の変数は削除してください。

variableScope(CWE-398)

変数のスコープを縮小できます。特定のブロック内でしか使用しない変数は、ブロック内で宣言することで保守性が向上します。

[不適合例]

int ret;
while (1) {
  ret = function(x);
  if (ret == 0) {
    break;
  }
  ...
}

[適合例]

while (1) {
  int ret;
  ret = function(x);
  if (ret == 0) {
    break;
  }
  ...
}
目次