makeで大量のメッセージが表示されるときは-dオプションを疑う もしくは変数展開についてのbash/zshの違い

ある私的プロジェクトでMakefileの変数$(CXXFLAGS)を変更して実行するために、以下のようなスクリプト script.sh

CXXFLAGS="-std=c++0x -stdlib=libc++"
make CXXFLAGS=${CXXFLAGS}

を書きました。このスクリプトの意図は、Makefileに記されたCXXFLAGSという変数を、"-std=c++0x -stdlib=libc++"という文字列で上書きして、Makeを実行することです。

このスクリプトを、bashで実行します。

$ bash script.sh

すると、以下のような数百行にも渡る大量のメッセージが表示されました。

GNU Make 4.1
このプログラムは i686-pc-cygwin 用にビルドされました
Copyright (C) 1988-2014 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 以降 <http://gnu.org/licenses/gpl.html>
これはフリーソフトウェアです: 自由に変更および配布できます.
法律の許す限り、 無保証 です.
makefile を読み込みます...
makefile 'Makefile' の読み込み中...
makefile の更新中....
 ファイル 'Makefile' を検討しています.
  'Makefile' のための暗黙ルールを探します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.o' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.c' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.cc' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.C' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.cpp' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.p' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.f' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.F' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.m' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.r' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.s' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.S' を試します.
  語幹 'Makefile' とのパターンルールを試します.
  暗黙の必要条件 'Makefile.mod' を試します.

(以下400行ほど続く)

   語幹 'test_constructor.cpp' とのパターンルールを試します.
   暗黙の必要条件 'MyVectorTest/SCCS/s.test_constructor.cpp' を試します.
   'MyVectorTest/test_constructor.cpp' のための暗黙ルールがありません.
   ターゲットファイル 'MyVectorTest/test_constructor.cpp' の必要条件を満たしました.
  ターゲット 'MyVectorTest/test_constructor.cpp' を再make する必要はありません.
 ターゲットファイル 'test' の必要条件を満たしました.
ターゲット 'test' を再make する必要があります.
ターゲットファイル 'test' の再 make に成功しました.

賢明な皆様はお気づきかもしれませんが、原因はbashの引数の展開方法にあります。

make CXXFLAGS=${CXXFLAGS}

の部分が

make CXXFLAGS=-std=c++0x -stdlib=libc++

と展開されてしまったおかげで、CXXFLAGSに代入される値が -std=c++0x だけになってしまいました。 残りの-stdlibの部分で、makeに-dオプションを渡したと誤解されてしまい、先ほどのような冗長なデバッグ情報が表示されてしまったようです。

初めの意図通りの処理をさせるには、変数の前後をクオートで囲むのが正解でした。

CXXFLAGS="-std=c++0x -stdlib=libc++"
make CXXFLAGS="${CXXFLAGS}"

bashスクリプトでのクオートについては Quoting Variables が詳しいです。以下のスクリプト

List="one two three"

for a in $List
do
  echo "$a"
done

echo "---"

for a in "$List"
do
  echo "$a"
done

を実行すると、クオートの有無による動きの違いがよくわかります。上記をshやbashで実行すると、

one
two
three
---
one two three

と表示されます。

なお、ややこしいことに、私の手元のzshでこのスクリプトを実行すると、

one two three
---
one two three

と、クオートがあろうがなかろうが同じ結果になってしまいました。shell - Variable expansion is different in zsh from that in bash - Stack Overflow によると、これがzshのデフォルトの振る舞いのようです。

zshでもbashと同じルールで変数展開したい場合は、

ls ${=args}

とするか

setopt SH_WORD_SPLIT

とすればよいようです(未確認)。

cmathをincludeするとy0, y1などのグローバル変数が使えなくなる問題

一見問題なさそうな以下のコード

#include <cmath>

int x1, y1;

int main() {
    x1 = 10;
    y1 = 5;
    return 0;
}

を、例えばVisual C++ 2015でコンパイルしようとすると、以下のエラーが出て失敗します。

main.cpp(3): error C2365: 'y1': redefinition; previous definition was 'function'

原因は、cmathにて、第二種ベッセル関数を求める関数y0(), y1(), yn()が定義されていることです。実際にこのエラーにぶつかるまでまったく知りませんでした。 C++系のヘッダファイルであればすべてstd名前空間の下で関数が提供されるものかと思っていましたが、そうではないようです。

変数名を変更する、またはグローバル変数からローカル変数に変更することで上記エラーを回避できます。以下は後者の例。

#include <cmath>
int main() {
    int x1 = 10;
    int y1 = 5;

    return 0;
}

ここからは余談です。cmathで提供されるy1()関数を使う以下のコード

#include <iostream>
#include <cmath>

int main(void)
{
    std::cout << y1(10) << std::endl;
    return 0;
}

は、g++ 4.9.3では問題なくコンパイルできますが、VC++2015でコンパイルしようとすると、

error C4996: 'y1': The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name: _y1. See online help for details.

というエラーが出てコンパイルに失敗しました。指示の通りy1を_y1にするとVC++2015でコンパイルできますが、今度はg++でコンパイルできなくなります。また一つ不幸の種が生まれたという感じです。

Visual Studio 2015起動時の"License: Prerelease software This license has expired"問題の解決策

Visual Studio 2015を起動すると、"License: Prerelease software This license has expired"というエラーメッセージが出て、開始できない問題に遭遇しました。

Visual Studio 2015 - Prerelease software. This license has expired - Stack Overflow にある通り、

「プログラムと機能」からVisual Studio 2015を選択して、修復(Repair)することで、問題が解決しました。

ScanSnap S1300iで読み込むJPEGの画像サイズと品質

FUJITSU ScanSnap S1300i FI-S1300A で本格的にスキャンをするにあたり、画像の圧縮率を1(低圧縮)から5(高圧縮)まで変化させると、画像サイズと品質がどう変化するかを調べました。

実験環境

  • スキャン対象
    • A4のカラー写真を含む紙片面
  • 主な設定
    • 読み取りモード:スーパーファイン (300ppi) または エクセレント (600ppi)   * 読み取り面の設定:片面読み取り
    • ファイル形式:JPEG

実験結果

まずは300ppiの場合。

設定 画像サイズ Q値
1(低圧縮) 2,877KB 95
2 1,202KB 80
3 847KB 67
4 550KB 45
5(高圧縮) 231KB 15

設定4,5はブロックノイズが目立ち、画像の鑑賞には向かないと感じました。 設定2と3では2の方がノイズが少ないと感じましたが、1と2ではあまり違いが分かりませんでした。 より正確に言うと、同一の紙をスキャンするたびに出るゆらぎの大きさが、設定1と設定2の画質の差を上回っているように感じました。

次に600ppiの場合。

設定 画像サイズ Q値
1(低圧縮) 12,477KB 95
2 5,519KB 80
3 4,901KB 67
4 2,673KB 45
5(高圧縮) 878KB 15

当たり前ですが、600ppiは明らかに300ppiよりも解像度が高いです。

こちらも設定4, 5はブロックノイズが気になりました。 設定1, 2, 3では、画像を大きく引き伸ばさない限りは、ファイルサイズほどの差は感じず、いずれも綺麗でした。

Q値は、ImageMagickで以下のコマンドを実行して調べています。(参考:image processing - Is it possible to tell the quality level of a JPEG? - Stack Overflow

$ identify -verbose (filename) | grep -i quality

Modern C++でのクラスに関する私的メモ

C++のクラスについて理解があやふやなので、調べたことを以下にまとめます。C++11以降をターゲットにしています。

特殊メンバ関数 (Special member functions)

以下に、メンバ変数を2つとメンバ関数を1つのみ持つ単純なクラスを含むサンプルを示します。

#include <iostream>
#include <cstring>

class Person {
private:
    int m_age;
    std::string m_name;

public:
    void setMembers(int age, std::string name) {
        m_age = age;
        m_name = name;
    }
};

int main() {
    Person obj1;  // デフォルトコンストラクタ
    obj1.setMembers(30,  "George");    

    Person obj2(obj1);             // コピーコンストラクタ
    Person obj3 = obj1;            // コピーコンストラクタ
    obj3 = obj1;                   // コピー代入演算子
    auto obj4 = std::move(obj1); // ムーブコンストラクタ
    obj1 = std::move(obj4);        // ムーブ代入演算子

    return 0;
}

Personクラスには必要最低限しか記述されていないにもかかわらず、Personクラスのオブジェクトに対して様々な処理を行えるのは、特殊メンバ関数(Special Member Functions)と呼ばれる、以下の6つの特別なメンバ関数が暗黙的に定義されるおかげです。

特殊メンバ関数が暗黙的に定義されない場合

ややこしいことに、ある関数をプログラマが明示的に定義すると、上記Special Member Functionのうちのいくつかが、暗黙的に定義されなくなる仕様になっています。

有名なところでは、もし引数を一つ以上もつコンストラクタが明示的に定義された場合、デフォルトコンストラクタは暗黙的に定義されなくなります。

以下にその例を示します。引数付きコンストラクタを明示的に定義しているせいでデフォルトコンストラクタが作られないので、コンパイルが通りません。

#include <iostream>
#include <cstring>

class Person {
private:
    int m_age;
    std::string m_name;

public:
    Person(int age, std::string name)
        : m_age(age)
        , m_name(name) {
    }
};

int main() {
    Person obj1; // エラー! デフォルトコンストラクタが存在しない

    return 0;
}

コンパイルを通すには2つの方法があります。1つ目は、defaultキーワードを使ってデフォルトコンストラクタを明示的に作らせる方法です。これはC++11以降可能になった方法です。

class Person {
private:
    int m_age;
    std::string m_name;

public:
    Person(int age, std::string name)
        : m_age(age)
        , m_name(name) {
    }

    Person() = default;
}

2つ目は、従来通りの定義方法です。

class Person {
private:
    int m_age;
    std::string m_name;

public:
    Person(int age, std::string name)
        : m_age(age)
        , m_name(name) {
    }

    Person() : m_age(-1), m_name("no name") {};
};

特殊メンバ関数が暗黙的に定義されない場合の詳細については、Special member functions - Wikipedia, the free encyclopedia や、 これを見やすい表にした 特殊メンバ関数とコンパイラによる暗黙宣言 - yohhoyの日記 にまとまっています。

Rule of Three

英語圏C++に関する記事を読むと、しばしば「Rule of Three」という用語が出てきます。これは、「デストラクタ、コピーコンストラクタ、コピー代入演算子のうち少なくとも1つをプログラマが明示的に定義したのであれば、残りについても、プログラマが明示的に定義しなければならない可能性が高い」という経験則を表す標語のようです。(参考:Rule of three (C++ programming) - Wikipedia, the free encyclopedia), c++ - What is The Rule of Three? - Stack Overflow

上で述べた関数のうちの一つをプログラマが明示的に定義したということは、例えばリソース管理などセンシティブな処理をクラス内で行っている可能性が高いです。ということは、他の関数についても明示的に定義して適切な処理を行わないと、リソース漏れなどが発生する可能性が高い、といえます。

上記3関数のうちの1つが明示的に定義されたら、残りの2つの関数を明示的に定義しないようにコンパイラが強制してくれればよいのですが、現状ではそうなっていません。そのため、プログラマは、上記3関数のいずれも明示的に定義しない(コンパイラの自動生成に任せる)か、もしくは上記3関数とも明示的に定義するかのどちらかを、責任をもって行うのがよいプラクティスになります。

Pythonの関数のデフォルト値にリストなどを使うときは要注意

Pythonの関数には、他の多くの言語と同じく、引数のデフォルト値を指定する機能があります。 例えば以下の関数に引数を一つのみ与えたときは、bには3の値が入ります。

def func1(a, b = 3):
    return a + b

このデフォルト値に、リスト、辞書などの可変オブジェクトを指定するときは要注意です。デフォルト値は最初の一度しか評価されず、関数を呼ぶたび値が変わってしまう可能性があります。

例えば以下のコード

def func2(a, b = []):
    b.append(a)
    print(b)

func(7)
func(8)
func(9)

を実行すると、結果は以下になります。

[7]
[7, 8]
[7, 8, 9]

参考

Nexus 5 + iijmioの組み合わせが不調のときに調べること

Nexus 5 + iijmioの組み合わせで通信が不調になることが何回かありました。この記事ではその解決策をまとめます。

Android 5.xにて通信が不安定になる問題

Androidを4から5に上げた後、アンテナマークにビックリマークが付いた状態になり通信できなくなることがありました。時間が経ったり再起動したりしたら治ることもありましたが不安定でした。

この現象は、iijmio動作確認端末 | IIJmio のページにて、以下のように説明されています。

Android 5ではIPv6に対応したIIJmioのAPN「IIJmio(LTE端末)」を選択することでIPv6での通信が可能です。通信が不安定な場合は、選択したIIJmioのAPN以外のAPNを全て削除してください。

この指示通り、IIJmio以外のAPNを削除することで問題が解決しました。

通信がまったくできなくなる問題

同じくアンテナマークにビックリマークが付いた状態になり、再起動してもまったく通信できなくなることがありました。

原因は、「設定」→「データ使用量」→「モバイルデータ」がいつのまにかOFFになっていたことでした。これをONにすると、無事通信できるようになりました。