旧ソ連の冷蔵庫

1

旧ソ連では冷蔵庫工場の生産性を測る基準として、生産した冷蔵庫の総重量(トン数)が使われていた。そのため、ロシア人は大量の鉄を使い、非常に重たい冷蔵庫を作った。

2

ある冷蔵庫工場に優れた技術者がいた。彼は人民のために軽くて使いやすい冷蔵庫を設計した。
数ヵ月後、党から工場に対して叱責が下された。他の工場よりも生産性が低かったからだ。党からの手紙には「人民に対する反逆」という文言が含まれていた。
数週間後、優秀な技術者はアメリカへ亡命した。

3

ある冷蔵庫工場に優れた技術者がいた。彼は冷蔵庫の改良を命じられた。技術者は無駄な部品を排除し、スペースを広げ、なおかつ冷却効率を向上させた。
数ヵ月後、工場は人民に対する反逆のかどで叱責された。
技術者はアメリカへ亡命した。

4

ある研究所が画期的な冷却技術を開発した。その技術を採用すれば、冷却装置の重量・スペースは 60% 程度に節約される上、冷却効率は 80% 程度向上すると見込まれていた。
誰もその技術で冷蔵庫を作らなかった。
研究所の職員は半分以上アメリカへ亡命した。

5

総トン数ではなく、台数や機能数で冷蔵庫を評価してほしいと申し出た工場もあった。しかし党の職員たちはこう言った。「台数を数えるのは大変だから」「私は数えられるが、数えられない職員もいるから」「生産計画は総トン数に基づいて三年先まで立てられている。すぐには変えられない」
幸い、生産計画が更新される前に旧ソ連は消滅した。

まとめ

ステップ数で生産性を測ろうとするなんて、あなたは頭がおかしいんじゃないのか。

nPOP メールボックスを Thunderbird に移行する Ruby スクリプト

nPOP はメールサーバのデータ管理に便利な軽量メーラーです。サーバ上から特定のメールを削除したり、メールの先頭だけを取得したりといった比較的低レベルのメール操作に威力を発揮します。
今日は訳あって nPOP でダウンロードしたメールデータをメインメーラーThunderbird に取り込む必要があったのですが、 google:npop thunderbird 移行] や [google:npop thunderbird 乗り換え で調べた限りでは、データ移行の方法が見つかりませんでした。というわけで、 MailBox*.dat を直接眺めて、それらしい変換スクリプトを書いてみました。動作無保証です。

npop2Thunderbird.rb

require 'delegate'
require 'time'
require 'nkf'

class NPopData < SimpleDelegator
	def initialize
		super([])
	end

	attr_accessor:time

	def put_data(out)
		out.puts "From - #{@time.rfc822}"

		each {|l|
			out.puts NKF.nkf( '-j', l )
		}
		clear()
	end
end

d = NPopData.new

while line = gets
	if /^\.$/ =~ line

		d.put_data(STDOUT)

	elsif /^Date\: (.+)$/ =~ line

		t = Time::parse($1)
		d.time = t

	else

		d.push line

	end
end

d.put_data(STDOUT)

使い方

  • Thunderbird を一旦終了させる。
  • nPOP でメールを取り込む。
  • スクリプトの引数にメールボックスファイルを指定する。標準出力に変換結果を吐くので適宜リダイレクト。

ruby npop2Thunderbird.rb MailBox0.dat > tmp

これでフォルダ一覧の中に tmp フォルダができるはずです。あとはメールを受信トレイなどにコピーして tmp フォルダを削除すれば OK 。 nPOP Ver 1.0.1 & Thunderbird バージョン 1.5 (20051201) で動作確認しました。

メモ

  • Thunderbird のメールボックス形式は Unix mbox 形式らしい。
    • From - #{日付}がメール一通一通の区切り?
    • 面倒なので RFC 読んでません。
  • nPOP のデータは独自形式に見える。
    • メールの区切りは .(ピリオド)だけの行?

CppUnit テストをプラグイン化

"CppUnit 1.10.2のTestPlugInの作成法について"

  1. テスト実行時のカレントフォルダは DLL のロード位置に設定されるらしい。テスト用データの位置を相対パスで書いていたので、これは重要。
  2. CppUnit-1.10.2 の TestPlugInRunner を使っているけど、なぜか setup() に失敗したり、連続で Run するとアプリケーションエラーになったりする。原因不明。
    1. 1 度目の実行では setup() が失敗。
    2. 2 度目の実行は成功。
    3. 3 度目の実行でアプリケーションエラー。
スレッド ID 0x728 のステート ダンプ

eax=00000000 ebx=000001a5 ecx=00000000 edx=00340ee8 esi=00340000 edi=003401c0
eip=77fcb333 esp=1097ef5c ebp=1097f128 iopl=0         nv up ei ng nz na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283


ファンクション: RtlAllocateHeap
        77fcb30f 884705           mov     [edi+0x5],al                 ds:00e4a0a6=??
        77fcb312 ff75d0           push    dword ptr [ebp+0xd0]   ss:1148900e=????????
        77fcb315 8b75a4           mov     esi,[ebp+0xa4]         ss:1148900e=????????
        77fcb318 56               push    esi
        77fcb319 e8d120fdff       call    RtlIsValidIndexHandle+0x182f (77f9d3ef)
        77fcb31e 8b4dd0           mov     ecx,[ebp+0xd0]         ss:1148900e=????????
        77fcb321 8b4108           mov     eax,[ecx+0x8]          ds:00b09ee6=????????
        77fcb324 8985d4feffff     mov     [ebp+0xfffffed4],eax   ss:1097effc=00000000
        77fcb32a 8b490c           mov     ecx,[ecx+0xc]          ds:00b09ee6=????????
        77fcb32d 898dd0feffff     mov     [ebp+0xfffffed0],ecx   ss:1097eff8=00000000
フォールト ->77fcb333 8901             mov     [ecx],eax              ds:00000000=????????
        77fcb335 894804           mov     [eax+0x4],ecx          ds:00b09ee6=????????
        77fcb338 3bc1             cmp     eax,ecx
        77fcb33a 7531             jnz     77fd3e6d
        77fcb33c 8b45d0           mov     eax,[ebp+0xd0]         ss:1148900e=????????
        77fcb33f 668b00           mov     ax,[eax]                   ds:00000000=????
        77fcb342 663d8000         cmp     ax,0x80
        77fcb346 7325             jnb     RtlAddRange+0x1e9 (77fcc26d)
        77fcb348 0fb7c8           movzx   ecx,ax
        77fcb34b 8bc1             mov     eax,ecx
        77fcb34d c1e803           shr     eax,0x3
        77fcb350 8985c8feffff     mov     [ebp+0xfffffec8],eax   ss:1097eff0=ffffffff

*----> スタック バック トレース <----*

FramePtr ReturnAd Param#1  Param#2  Param#3  Param#4 ファンクション名
1097F128 78001532 00340000 00000000 00000010 00ECD7C8 ntdll!RtlAllocateHeap 
1097F168 780014CF 00000008 780197B2 00000008 00000001 !malloc 
0000000E 00000000 00000000 00000000 00000000 00000000 !malloc 

Doxygen とプリプロセッサ

上の話題に関連して、 C/C++ プリプロセッサRuby で実装した人がいないか探していたら、こんなページを発見。

クラスのメンバメソッドをマクロで宣言すると、 Doxygen なんかで展開したときに解析されなくて Bad だよねー、という話。そうそう。うちにもそういうクラスがあるんです。しかもたくさん。
このページのコメントによると、 doxyfile で EXPAND_AS_DEFINED を設定しておくとよいとか。
というわけで自前の doxyfile を眺めてみると、確かにそれらしい設定があります。眺めてみた限りだと、

# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
# this tag can be used to specify a list of macro names that should be expanded.
# The macro definition that is found in the sources will be used.
# Use the PREDEFINED tag if you want to use a different macro definition.

どうやら MACRO_EXPANSION を YES にしろと言っているようです。というわけで、こんなダミープログラムを書いて実験してみました。
サンプルコード

#include <stdio.h>

#define DECL_METHOD \
    void test()  { puts("test");  } \
    void test2() { puts("test2"); }

class Test {
public :
    DECL_METHOD
};

class Test2 : public Test {};
class Test3 : public Test {};

void main() {
    Test2 t2;
    t2.test();
    t2.test2();

    Test3 t3;
    t3.test();
    t3.test2();
}

このあと doxygen -g test.doxyfile で設定ファイルを生成して、

MACRO_EXPANSION = YES
HAVE_DOT = YES
UML_LOOK = YES

として実行。(クラス図を生成)
結果

確かにマクロ展開後のソースで解析してくれているようです。

プリプロセッサ代わりに Ruby を使う

後輩からこんな質問を受けました。
「こういうマクロの展開結果を 16 進数で出したいんですけど、簡単な方法ありませんか?」

(EVENT_DEF.h)

#define ID_MODULE1 8
#define PRIORITY   2

#define EVENT_A_CHANGE_STATE       ((2<<24)+( 0<<16)+(ID_MODULE1<<8)+PRIORITY)
#define EVENT_A_CHANGE_RESULT      ((2<<24)+( 1<<16)+(ID_MODULE1<<8)+PRIORITY)
// 以下続く…

一番単純な方法はこんなプログラムを書くことです。

#include "EVENT_DEF.h"
void main() {
    printf( "EVENT_A_CHANGE_STATE  - %08x\n", EVENT_A_CHANGE_STATE );
    printf( "EVENT_A_CHANGE_RESULT - %08x\n", EVENT_A_CHANGE_RESULT );
    // 以下延々続く…
}

しかし、 define が数百もある場合はそうもいきません。全ての定義の printf() を書かなければならないからです。ぞっとしませんね。
次に考えたのは、プリプロセッシング後のソースを見ることです。 VC++ 6.0 でやる場合、 cl.exe の引数に /P (/EP)を指定しろと MSDN に書かれています。というわけで、上の単純なプログラムの設定を書き換えてみました。[プロジェクトの設定] -> [C/C++]タブのプロジェクトオプションで

/nologo /MLd /W3 /Gm

/nologo /P /EP /MLd /W3 /Gm

に変更。これでソースコードと同じ場所に *.i が出力されます。結果、こんなファイルが出てきました。

void main()
{
    printf( "EVENT_A_CHANGE_STATE  - 0x%08x\n", ((2<<24)+( 0<<16)+(8<<8)+2) );
    printf( "EVENT_A_CHANGE_RESULT - 0x%08x\n", ((2<<24)+( 1<<16)+(8<<8)+2) );
}

えー。
…というわけで、結局 Rubyプリプロセッサライクなスクリプトを書くことに。

(pp.rb)

File.open( ARGV.shift ) { |f|
  f.each_line {|l|
    if l =~ /^\#define\s(\w+)\s+(.+)$/

      name, s = $1, $2

      # 定義を置換
      s.gsub!( /ID_MODULE1/, 8.to_s )
      s.gsub!( /PRIORITY/,   2.to_s )

      printf "%s\t0x%08x\n", name, eval(s).to_i
    end
  }
}

これで

ruby pp.rb EVENT_DEF.h

として、ようやく次のような出力結果が得られました。やれやれ。

EVENT_A_CHANGE_STATE    0x02000802
EVENT_A_CHANGE_RESULT   0x02010802

スレッド関数内の auto 変数

初回ということで、 id にちなんで std::auto_ptr で痛い目に遭った経験を。

どのくらい有名な話かわからないのですが、 _endthread() などをスレッド関数の中から呼び出した場合、スレッド関数のスコープ内にある auto 変数は解放されません。つまり、 auto_ptr または自作スマートポインタが機能しなくなるのです。ローカル変数の解放をスマートポインタに任せていると全てリークします。

サンプルコード

#include <stdio.h>
#include <process.h>
#include <memory>

class Test {
public :
    Test() { printf( "Test new\n" ); }
    virtual ~Test() { printf( "Test delete\n" ); }
};

void testThread( void* param ) {
    std::auto_ptr< Test > test( new Test );

    _endthread();
}

void main() {
    _beginthread( testThread, 0, NULL );

    getchar();
}

実行結果

Test new

このように、 Test クラスのデストラクタは実行されません。スレッド終了に _endthreadex() / ExitThread() を使った場合も同様です。

対策として最も楽な方法は、スレッド関数の中でスコープを切ってやることです。

void testThread( void* param ) {
    {
        std::auto_ptr< Test > test( new Test );
    }

    _endthread();
}

実行結果

Test new
Test delete

こうすれば、 _endthread() 前にスコープの終了が来るので、その時点でスマートポインタが解放されます。つまり、スレッド終了関数をコールする前に必ずスコープを切っておけば OK です。
なお、「スレッド関数の中で goto を使っている場合はどうすればいいか」は考えていませんので悪しからず。スマートポインタと goto を併用するような真似はやめましょうとしか言えません。

目的

この日記の目的は、ひとことで言ってノウハウの共有化です。

私は過去のソフトウェア開発において、インターネットから非常に多くのものをもらいました。しかし、今までに私がインターネットに対して貢献した量はごく僅かです。これは明らかに不自然です。言い方はいろいろありますが - アンフェアとか、ただ乗りとか、寄生虫とか -、まあ、少なくとも非対称的であることは間違いないでしょう。

そこで、「カエサルのものはカエサルに返せ」ではありませんが、インターネットから得た知識をインターネットに返すことにしました。

こう書くといい話に聞こえますが、私は別にプログラマ的良心のみに従ってそうするわけではありません。ごく個人的かつ現実的な理由のために、ノウハウを公開した方が私にとっても有利なのです。たとえば、後輩がこんなコードを書いたとしましょう。(言語は C++ です)

 void test(int c) {
     TestClass* test = new TestClass();
     test->set(c);
 }

彼を「バカじゃないの」と叱るのは簡単です。また、私がコードを全て書き直すのも簡単です。しかし、私はそんな馬鹿馬鹿しい作業にいちいち付き合うほど暇でも親切でもありません。このコードを最低量の修正で後輩自身に直させるとしたら、おそらく次のどれかをアドバイスするでしょう。

  1. 「 delete しろ」
  2. 「スタックに置け」
  3. 「スマートポインタ使え」

そこで後輩がこう聞いてきたらどうするでしょうか。

  1. 「スタックって何ですか」
  2. 「スマートポインタって何ですか」

言うまでもなく、私はいちいち解説してあげるほど暇でも親切でもありません。「そんなこといちいち聞くな。黙ってググれ」と言いたいのです。そうです。この日記の目的はまさにそこにあります。黙ってググったときにこの日記がヒットするように、そして私の説明の手間が省けるようにしたいのです。

と、偉そうなことを言いましたが、私自身もそれほどレベルの高いプログラマではないので、自分でも「あれ、これ前にも困ったけど、結局どうしたっけ」と思うことが多々あります。ノウハウを記録しておけば、そういうときのための備忘録にも使えるのではないかと思った次第です。