2018年8月8日水曜日

Ubuntu 18.04劇遅!!

なんか、最近、Virtual Box上で動かしてる Ubuntu 18.04がとても遅い。
絶えられないくらい。
端末1つ出すだけで2~3分、平気で掛かったりする。

他の人のも同様だった。
いままで RAMは 1,024MB割り当てていたが、これを 2,048MBにしたら嘘のように速くなった。
Ubuntu 17.10→18.04でメモリ消費が増えたようだ。
だれ(どのプロセス)が浪費してるかは調査していない。

2017年9月28日木曜日

Ubuntu環境で cppcheckを使ってみた

SonerQube用のプラグインを入れて Cのコードをスキャンさせたら解析結果は空っぽで、ソースコードのみが表示できるだけになってしまった。
どうやら Javaの場合とちがって、C/C++用のプラグインはコードの解析を他のツールに委譲し、結果の集計と整形を行うだけの機能しか無い様だ。
なので、オフィシャルの情報に従ってツールを入れることにした。
解析ツールは複数の選択肢があるが、最初に示されているのが cppcheckというヤツだ。
ソースコードの中の危ない箇所や怪しい箇所を見つけて指摘してくれる。

インストール

Ubuntu 17.04使ってるので aptで cppcheckを installするだけ。
事前に updateしても1分と掛からない。
インストール後の OS再起動も不要。

実行

個別のソースを指定して解析することもできるが、SonarQubeを使うときは対象のディレクトリにあるのを一度にやりたいので --ename=allを指定する。
こんな感じだ。
cppcheck --xml-version=2 --enable=all . 2> cppcheck.xml
--xml を指定すると XML形式のレポートが出力される。 --xml-version=2はそのバージョン2ということで、出力される内容が少し詳しくなっている。

この例で "." を指定してるのは解析するソースを探すパスで、カレントを指定している。これは絶対パス、相対パスのいづれかを指定すればよい。

標準エラーに出力された内容を cppcheck.xmlファイル(名前は任意で良い)に出力する。

それでは次のコードを解析してみる。(一目見て怪しいコードだと解かるでしょう。チェック対象だから何らかのエラーか警告が欲しいのでわざとやってるのです。普段からこんな危険なコード製品に組み込んだりはしてませんから…)
#include <stdio.h>

static int* getPointer() {
 int value;
 return &value;
}

int main() {
 printf("Hello, world.\n");
 int* p = getPointer();
 if (*p != 0) {
  *p = 2;
 }
 printf("[INFO] *p(%p): %d\n", p, *p);
 return 0;
}

結果は次のとおり、エラーが含まれている。
<?xml version="1.0" encoding="UTF-8"?>
<results version="2">
    <cppcheck version="1.76.1"/>
    <errors>
        <error id="returnAddressOfAutoVariable" severity="error"
        msg="Address of an auto-variable returned."
        verbose="Address of an auto-variable returned." cwe="562">
            <location file="Main.c" line="5"/>
        </error>
        <error id="missingIncludeSystem" severity="information"
        msg="Cppcheck cannot find all the include files (use
        --check-config for details)"
        verbose="Cppcheck cannot find all the include files. Cppcheck
        can check the code without the include files found. But the
        results will probably be more accurate if all the include files
        are found. Please check your project's include directories
        and add all of them as include directories for Cppcheck. To see
        what files Cppcheck cannot find use --check-config."/>
    </errors>
</results>
このままでも役に立つけど、SonarQubeでより見やすくすることができる。SonarQubeに関する報告は、またの機会とする。
実際のプロジェクトで使用するにはコンフィグレーションも必要になるが、Softiesプロジェクトでは少しずつ調整して行くことにする。

2017年9月24日日曜日

コードカバレッジツール kcovを使ってみた

Softiesプロジェクトは 2006年から gcovツールを使っているが、シェルスクリプトのカバレッジを計測することができる kcovというのがあるのを最近になって知ったので、早速使ってみた。


結論から言えば、『なんとか使える』レベル。「ないよりはマシ」。


bashスクリプトのカバレッジを計測するのた目的で、今回使用した環境は 64bit版 Ubuntu 17.04。

aptで簡単にインストールできたが、実は 2日前に別の Ubuntu 17.04環境にインストールしたときはインストールは成功したものの、実行すると C++のライブラリで異常な配列インデクスが原因でコアダンプしてしまう現象が発生した。ソースからダウンロードして cmakeからやってみたが解決しなかった。そのときの詳しい経過やエラーメッセージは持ち出しできないので再現できず原因は不明だが、依存するライブラリとの相性問題があるかも知れない。(訂正:その動かなかった環境のものも動いた。エラーが発生したのは kcovと一緒にインストールしたテストフレームワークのものだった。)

機能の簡単な紹介

  • ELF, Mach-Oの両形式でコンパイルされたバイナリ、Python、bash、shプログラムのカバレッジを計測するテスタで、Linux/OSXで動作
  • オプションなしにコンパイルされたバイナリから直接カバレッジを取得
  • 特別な工程なしに HTML, Cobertura XMLを出力
詳しい情報は https://simonkagstrom.github.io/kcov/ を参照

実行状況

とりあえず今日動かした状況は下記のとおり。
$ kcov d ~/bin/bu
  • dは出力先のディレクトリ、その次に計測対象のプログラムを指定する。
  • 出力先ディレクトリは予め作っておく必要はない。
  • 逆に出力先ディレクトリに以前の計測結果があるときはマージされるので、まっさらな状況から計測したいときは空にしておかなければならない。
  • プログラムを指定するときは、絶対パスまたは相対パスを指定する必要があり、環境変数に指定したパスにあるものではプログラムは usageを表示してしまい計測できない。
  • コンパイル時にオプションが不要とのことだが、ビルドID ファイルがない場合は -gまたは -ggdbオプションでビルドする必要がある。(ビルドIDファイルって何だ?)
  • プログラムの後にプログラムに与えるオプションやオプション引数を指定できる。

レポート

前述のコマンドを実行するとつぎのような HTMLのレポートが得られた。
gcovでもよく使われれ、見慣れた lcov風の書式。いい感じ。

しかし、コードを表示させると妙な行間があって見にくい。

charsetの指定がないため、文字化けも発生。 手動で UTF-8を選択すれば正しく表示されるが、ページを移動するたびにやらなければならない。

doneや fiのカウントが可笑しい。(他のカバレッジツールでも起こる問題だが)



いつくか難点はあるものの、ここまで作ってくれた作者に感謝するとともに、今後の改善を祈りながら使っていきたいと思う。
冒頭で「ないよりマシ」とか酷い書き方をしたが、実際には「使えねぇ」ものも数ある中では、使い続けようかという気にさせたツールの1つである。

2017年5月19日金曜日

Clangでユニバーサルキャラクタ識別子を使う

Softiesプロジェクトとは直接は関係ない話題。

1年前から Softiesプロジェクトがこれまで使用してきた gccを clangに置き換える方向で準備を進めている。
と言っても gccを廃すかどうかまでは決まって折らず、平行運用することになるかも知れない。

そんな中、gccでは使えないユニバーサルキャラクタ識別子が clangでも使えることが判った。
ちょっと、悪乗りした感じのプログラムを紹介する。
つぎのプログラムが Cで書いた物。実際には C99の仕様である。

main.c
#include "C文法定義"
#include "ライブラリ定義"

型定義 構造体 自動車* 自動車;

構造体 自動車 {
 文字列 車種;
 整数 価格;
};

整数 メイン関数(整数 argc, 文字列* argv) {
 自動車 俺の車 = (自動車)メモリ割り当て(サイズ(構造体 自動車));
 もし (俺の車 != ぬるぽ) のとき {
  俺の車->車種 = "プリウス";
  俺の車->価格 = 1234567;
 }
 表示("[INFO] %s, %d円\n", 俺の車->車種, 俺の車->価格);
 戻る 0;
}

ヘッダファイルはつぎの2つ


C文法定義
#ifndef _Included_C文法定義
#define _Included_C文法定義

#define 文字列  char*
#define でないとき else
#define もし  if
#define 整数  int
#define 戻る  return
#define サイズ  sizeof 
#define 構造体  struct
#define 型定義  typedef
#define メイン関数 main
#define のとき

#endif /* _Included_C文法定義 */

ライブラリ定義
#ifndef _Included_ライブラリ定義
#define _Included_ライブラリ定義

#include 
#include 

#define ぬるぽ  NULL
#define 表示  printf
#define メモリ割り当て malloc

#endif /* _Included_ライブラリ定義 */

ついでに必要最小限の Makefile
CC = clang

つぎのようにビルドして
make Main

実行する。
$ ./Main
[INFO] プリウス, 1234567円

Javaは初期の言語仕様から識別子に日本語が使えた。
しかし、#define機能がないのでキーワードまでは置き換えられない。

2016年10月21日金曜日

変数の値が非数 NaNかどうか調べるために、定数 NANと比較してはならない。

次に示すのは CUnitを用いたテストケースである。
/**
 * 定数 NANと比較すると偽を返すこと。
 *
 * NaNかどうか判定するために、定数 NANと比較してはならない。
 */
public void testCompareNanAndNan() {
        /* 事前条件の準備 */
        double value = NAN;

        /* 実行 */
        boolean result = (value == NAN);

        /* 事後条件の検証 */
        Assert_false("結果は偽であること。", result);
}
これを実行すると、テストは成功する。
変数 valueが定数 NANで初期化されているが、その変数を NANと比較しても真にならない。
比較演算子で NANと比較しても NaNかどうか判断できないということで、これは正しい挙動である。
なお、このテストケースは gccを使用してコンパイルしたが、定数 NANを使用するためには math.hをインクルードする必要がある。
ISOの C99よりも前のバージョンでは、処理系によっては NANが使えない。
NaNであるかどうかの正しい判定方法を次のテストケースに示す。
/**
 * NaNであることを判定できること。
 *
 * NaNかどうかの判定には isnan()を使用すること。
 */
public void testIsNan() {
        /* 事前条件の準備 */
        double value = NAN;

        /* 実行 */
        boolean result = isnan(value);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
isnan()も math.hが必要。
==比較演算子を使用して「強引に」判定する例を次に示す。
/**
 * ポインタを使用して NANと比較することで、NaNかどうかを判定できること。
 *
 * この使い方を推奨しているわけではないが、定数を代入しただけのものは
 * ビットパターンの比較により判断ができる。
 * この例ではポインタを使用したが、unionを用いても同様のことができる。
 *
 * NaNの値は1通りではないため、計算結果や外部から読み込んだ値を正しく
 * 判断するには == は使えない。
 */
public void testCompareNanAsPointer() {
        /* 事前条件の準備 */
        double value = NAN;
        double nan = NAN;

        /* 実行 */
        boolean result = (*(long long*)&value == *(long long*)&nan);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
ドキュメントコメントにも書いてあるよう、この方法では特定の条件でした判断できない。
非数 NaNを理解するための例をもう1つ示しておこう。
/**
 * NaNは 指数部の全ビットが 1で、仮数部が 0でないこと。
 *
 * これは例を示しただけで、実際にやるのは無駄。 isnan()を使うべき。
 */
public void testNanAsPattern() {
        /* 事前条件の準備 */
        double value = NAN;
        long long exponentMask = 0x7ff0000000000000;
        long long mantissaMask = 0x000fffffffffffff;

        /* 実行 */
        long long longValue = *(long long*)&value;
        boolean result = ((longValue & exponentMask) == exponentMask)
                && ((longValue & mantissaMask) != 0);

        /* 事後条件の検証 */
        Assert_true("結果は真であること。", result);
}
valueを定数 NANで初期化したものだけでなく、0.0 / 0.0した結果も正しく判断できる。
printfを使って調べると、gccの場合、NANのパターンは 0x7ff8000000000000、0.0 / 0.0の結果は 0xfff8000000000000で 1ビット異なるが、いづれも NaNと判定できた。
注:value = 0.0 / 0.0; というコードはコンパイル時点でエラーになる。 分母を変数にしてその変数を 0.0で初期化するようにすれば、コンパイルが通るようになる。

普段の判定は isnan()を使用すればよいが、isnan()が使えない場合や NaNを使ったテストケースを記述するとき、この知識は役にたつだろう。
なお、上記の例で使った CUnitテストフレームワークについてはこのブログで紹介してきたが、はじめてみる人のために簡単に説明しておく。
これらのテスト関数はつぎのようなコードを含むソースコードとして記述され、softiesの CUnitライブラリとリンクされる。
#define import_lwd_unit_Assert
#define import_lwd_unit_CUnit

#include <lwd/lwd.h>
#include <lwd/unit/Assert.h>
#include <lwd/unit/CUnit.h>
#include <math.h>

/**
 * @file
 *
 * NaNの振る舞いのテスト。
 */

/****************************************************************
        test cases
****************************************************************/

// ここに、テスト関数を記述する。
// テスト関数名が異なれば複数個記述可能。

/****************************************************************
        main
****************************************************************/

public int main(int argc, char** argv) {
        return CUnit_test(argc, argv);
}
main関数のなかで CUnit_test()を呼び出すことにより、自動的にテスト関数を認識して実行する。
なおテスト関数の名前は textXXXのように先頭4文字は testでなければならない。