旧ソ連の冷蔵庫
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
- Thunderbird のメールボックスフォルダに一時ファイル tmp を置く。
- Thunderbird 起動。
これでフォルダ一覧の中に tmp フォルダができるはずです。あとはメールを受信トレイなどにコピーして tmp フォルダを削除すれば OK 。 nPOP Ver 1.0.1 & Thunderbird バージョン 1.5 (20051201) で動作確認しました。
メモ
- Thunderbird のメールボックス形式は Unix mbox 形式らしい。
- From - #{日付}がメール一通一通の区切り?
- 面倒なので RFC 読んでません。
- nPOP のデータは独自形式に見える。
- メールの区切りは .(ピリオド)だけの行?
CppUnit テストをプラグイン化
"CppUnit 1.10.2のTestPlugInの作成法について"
- テスト実行時のカレントフォルダは DLL のロード位置に設定されるらしい。テスト用データの位置を相対パスで書いていたので、これは重要。
- CppUnit-1.10.2 の TestPlugInRunner を使っているけど、なぜか setup() に失敗したり、連続で Run するとアプリケーションエラーになったりする。原因不明。
- 1 度目の実行では setup() が失敗。
- 2 度目の実行は成功。
- 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); }
彼を「バカじゃないの」と叱るのは簡単です。また、私がコードを全て書き直すのも簡単です。しかし、私はそんな馬鹿馬鹿しい作業にいちいち付き合うほど暇でも親切でもありません。このコードを最低量の修正で後輩自身に直させるとしたら、おそらく次のどれかをアドバイスするでしょう。
- 「 delete しろ」
- 「スタックに置け」
- 「スマートポインタ使え」
そこで後輩がこう聞いてきたらどうするでしょうか。
- 「スタックって何ですか」
- 「スマートポインタって何ですか」
言うまでもなく、私はいちいち解説してあげるほど暇でも親切でもありません。「そんなこといちいち聞くな。黙ってググれ」と言いたいのです。そうです。この日記の目的はまさにそこにあります。黙ってググったときにこの日記がヒットするように、そして私の説明の手間が省けるようにしたいのです。
と、偉そうなことを言いましたが、私自身もそれほどレベルの高いプログラマではないので、自分でも「あれ、これ前にも困ったけど、結局どうしたっけ」と思うことが多々あります。ノウハウを記録しておけば、そういうときのための備忘録にも使えるのではないかと思った次第です。