教材としての yrmcds

@ymmt2005 こと山本泰宇です。 [公式発表][official]とは別に、こちらでは [yrmcds][] のもう一つの使い方を紹介します。それと、私の気持ちなどを。

yrmcds は実際のところ、作ることをかなりためらいました。工数という観点でみれば Apache が Redis を扱えるようにするとか、レプリケーションの仕組みを頑張るといったほうが小さい気がしたからです。C++11 の実践課題という動機は個人に寄っているので、会社に十分なメリットがないことには・・・と。

そこで会社へのメリットとして、以下の価値を加えることにしました。

  1. 後進の開発者が設計やプログラミング技法を学ぶ教材として使える
  2. 英語圏では無名のサイボウズの知名度の足しになるようにオープンソース化して宣伝を頑張る
続きを読む

R.I.P. 金子勇氏

金子勇氏の突然の訃報に驚きました。

氏とは数年ほどお付き合いする機会がありましたが、プログラマーとしての才能は疑う余地なく素晴らしいものでした。 あの悪名高い起訴騒ぎを勝ち抜いたことは、未来の才能あるプログラマーを委縮から救う、見事な業績であると感謝しています。

合掌

アセンブリ読んだら負けかなと思ってる

子供のころからできるだけ手抜きして成果を挙げることだけは長けている山本です。

今回は、C/C++ で作ったプログラムが運用中にクラッシュするときのデバッグ方法のお話しです。 開発中のデバッグgdb などでソース追いながらデバッグできますが、運用中ですと [strip][] していたり最適化していたりしてデバッグが難しくなります。 そもそも、いきなりクラッシュすると情報が残らずに困ってしまいます。そんなときどうするか。

Step1. スタックトレースを出力する

続きを読む

マージソートが書けなくても、プログラマになれる方法

久しぶりに社内でプログラミングのレクチャーをすることにしたのだけれども、準備をしていて自分がどうやってプログラマになったかをふと思い返してしまった。結論を書くとあっさり一言で済んでしまうのだけれども、やはり苦労話(?)もないと面白くないのでぐだぐだ書いてみる。

◆コンピューターとの出会い

小学三年生くらいの頃、読んでいた雑誌(確か子供の科学)にマイコンのことがのっていて、四則演算とか表示とかをする簡単な BASIC を見たのが最初です。ゲームウォッチみたいなものを自分で作れるような夢の機械に見えて、それからしばらくは本屋さんでマイコンの本を立ち読みしてました。

当時は英語も読めなかったので、たとえば PRINT のことをピー・アール・インテジャーと読んでいました。INTはインテジャーと読む、と別のところで覚えて、でも PRINT は読み方が分からなかったので。それでプログラミングを覚えられたかというと全くそんなことはなかったです。立ち読みの紹介記事ではさもありなん。

で、親にパソコンをねだり続けたわけですが、確か六年生のころかな、MSX2を買ってもらいました。正確には親のためのものだったんですが、親がキーボードも打てないのを良いことに事実上自分のものにしたのですけど。それでようやく本物のパソコンを触れるようになり、信長の野望初代は BASIC でかかれていたので、STOP すればソースが読めた)や Wizardry で遊びつつ、マシン語(当時はOS)も覚えようとしたのですが。。

全然だめでした。いや、Z80は読み書きできるようになったんですが、I/Oや割り込みの仕方を調べられなくて、面白くもなんともなかったというか。あの当時他の人はどうやってそういう仕様を学んでいたのか、いまだに不思議に思っていたりします。

◆コンピューターとの別れ

で、ゲーム専用機と化した MSX2 で毎日遊んでいたら、中学2年生の頃に、親にパソコン捨てられました(笑)。その後はコンピューターに触る機会なかったのですが、コンピューターを触る仕事に就くという点だけは譲りませんでした。親は医者にさせたかったようですが、頑として拒否した結果、医者でなければ東京大学に行けという妥協案を出されました。

第一希望は1年目からコンピューターを触れる(と思っていた。入らなかったので実際は不明)東工大の5類だったのですが、東大にはコンピューターを触れそうな専攻がないと思っていたので嫌でした。でも親(のお金)には逆らえず、しぶしぶ調べたところ、理学部情報科学科というところでは触れそうと分かりました。

入学後2年は専門ではなく教養、というのがまた嫌なところだったのですが。

◆再会と挫折

無事入学後も、何とかしてコンピューターに触りたかった私は、駒場の授業で「計算機入門」というコマをとることにしました。でもその授業は座学で、実際のコンピューターにはさらわず、ソートなどのアルゴリズムを紹介するものでした。まあでも、このとき初めて、プログラミングというのはマシン語を並べて I/O するだけじゃないんだ、と知ったわけですが。

その授業で知ったマージソートというアルゴリズムが簡単そうだったので、また別の授業(プログラミング入門だったかな?)で Pascal という言語を習ったときに、試しに実装しようとしてみました。・・・実装できませんでした。

今となってはなぜ当時の自分がこんな簡単なアルゴリズムを実装できなかったか分かりません。でも、事実として、作ったプログラムは動かなかったのです。再帰という概念やデータ構造といった基本的な道具立てがなければ、ごくシンプルなプログラムであっても難しい、のでしょう。

さらに、なぜ動かないのか教えてもらおうと思って計算機センターの相談員の人に見てもらいました。・・・教えてもらえませんでした。今となっては、読むに堪えない代物だったのだろうと思います。が、当時はマージソートというのはとても難しいものなのだな、と理解?したのでした。

とりあえず今日はここまで。

[C++] 例外を投げ捨てるのがあまりよろしくないわけ

前の記事で例外は投げ捨てみたいなことを書いておいてなんだが、投げ捨てるのはあまりよろしくない。

例外を catch しないと std::terminate() が呼び出されて最後は abort(3) されるのだけれども、この場合は RAII が効かず、スタックオブジェクトのデストラクタが呼び出されない。結果としてゴミが残ったりしてしまうことになる。std::set_terminate() でスタックトレースを出力するハンドラをセットして様子を見てみると以下のような出力が得られるはずだ。

$ ./test
Backtrace:
./test(_Z12my_terminatev+0x3b)[0x42249d]
/usr/lib/libstdc++.so.6(+0xcad16)[0x7fd612aa4d16]
/usr/lib/libstdc++.so.6(+0xcad43)[0x7fd612aa4d43]
/usr/lib/libstdc++.so.6(+0xcae3e)[0x7fd612aa4e3e]
./test(_ZN6cybozu4saveERKN4Poco4PathE+0xde)[0x422dd2]
./test(main+0x114)[0x4225f3]
/lib/libc.so.6(__libc_start_main+0xfd)[0x7fd6121dbc4d]
./test[0x4223a9]

cybozu::save() あたりから C++ のランタイムに入って、スタックが巻き戻ることなく my_terminate() が呼び出されているのが分かる。例外処理のランタイムはスタックを検査して対応する catch まで巻き戻るといったことをするわけだが、みつからないときは一切スタックを巻き戻さずにそのまま terminate 処理をするわけだ。

であるからして、やはり C++ で catch( ... ) するイディオムには意味はあるわけですな。

int main() {
    try {
        // do something
    } catch( const std::exception& e ) {
        //
    } catch( ... ) {
        //
    }
}

C++チップス

最近 120 年ぶりに C++ を書くことにしたので、もだーんとは何かという禅問答を繰り返した記録を公開。

C++ のススメ

なぜ C ではなく C++ を使うのか。その理由の 99.999% は std::string にある。
char* は忘れるんだ! std::string を使うだけで↓のように幸せになれる!

 
void f(const std::string& s) {
g("hoge "+s);
}

C ならこんな感じになるところだ。

void f(const char* s) {
char buf[256];
sprintf(buf, "hoge %s", s);
g(buf);
}

ライブラリ

STL は機能リッチとはとても言えないので、ちょっとなにかやりたいことがあるとライブラリをまず探すという面倒なことになる。まあ主要なところをいくつかまとめておく。

用途名称URLコメント
広範 Boost http://www.boost.org/ 事実上標準的な地位にある。機能はとても豊富。でもテンプレートメタプログラミングを駆使しているのでコンパイルがとても遅い。私は好きじゃない。
広範 POCO http://pocoproject.org/ MySQL ライブラリ等を備えていて boost よりも実用的アプリに向く。ネットワークアプリを作るのにとても便利。激しくお勧め。
マルチスレッド TinyThread++ http://tinythread.sourceforge.net/ ミニマリスト。生スレッドの 100 倍はマシ。
JSON picojson https://github.com/kazuho/picojson @kazuho 作。お勧め。

Tips

  • 初期化子
    メンバー変数の定義順(出現順)に初期化子も並べる必要がある。正確には、初期化子がどういう順序で書かれていようとも定義順に初期化がなされるので、初期化子の順番に初期化がされると思うととんでもない不具合になる。コンパイラで -Wall とかしておけば警告してくれる。
  • クラスメンバの初期化
    primitive はスタックオブジェクトだと 0 初期化されない。ので、必ず初期化子で初期化する。オブジェクトはデフォルトコンストラクタ呼んでくれるので初期化子で明示的に初期化する必要は普通はない。
  • Javaのようにオーバーロードしたコンストラクタを参照したい
    できません。コンストラクタは初期化だけすればよろしい。以上。
  • finally ないの?
    この Java 脳め!
    C++ はスタックオブジェクトはスコープを外れるとデストラクタを必ず呼んでくれるので、それを使えばリソースを確実に解放できる。auto_ptr などはそれを利用している。RAIIという。
    void f() {
    std::auto_ptr<T> t(new T(...));
    ... do anything ...
    // no need for delete t
    }
  • RAII
    Q. RAII はいいんだが、配列とかどう RAII するの?
    A. auto_ptr は配列には使えないので、vector でやる。
    std::vector<char> v(SIZE);
    char* buf = &(v[0]);
  • 例外安全
    new するな。以下のように auto_ptr と vector を使う。
    class C {
    C(...): m_object(new T(...)), m_array(SIZE) {}
    std::auto_ptr<T> m_object;
    std::vector<T> m_array;
    };
  • 例外
    stdexcept に基本的なのはある。
    なげるときはスタックオブジェクトでいい。処理系が例外オブジェクトにコピーしてくれる。
    なので、コピー可能なオブジェクトである必要がある。catch するときはそのコピーを参照で受け取る。
    #include <stdexcept>
     
    class MyException: public std::runtime_error { ... };
    try {
    throw MyException("error");
    } catch( const MyException& e ) {
    ...
    }
  • 例外は投げ捨てる漢へ
    バックトレース出力を追加すると幸せ。
    std::terminate_handler orig_handler = 0;
    const int MAX_BACKTRACE = 100;
    void my_terminate() {
    std::cerr << "Backtrace:" << std::endl;
    void* bt[MAX_BACKTRACE];
    int bt_num = backtrace(bt, MAX_BACKTRACE);
    backtrace_symbols_fd(bt, bt_num, 2);
    std::cerr << std::endl;
    if( orig_handler ) orig_handler();
    abort();
    }
     
    int main(int argc, char** argv) {
    orig_handler = std::set_terminate(my_terminate);
    throw std::runtime_error("Failed to open "+argv[0]);
    return 0;
    }

    実行例:

    Backtrace:
    ./tbs[0x402b4f]
    /usr/lib/libstdc++.so.6(+0xcad16)[0x7f040877cd16]
    /usr/lib/libstdc++.so.6(+0xcad43)[0x7f040877cd43]
    /usr/lib/libstdc++.so.6(+0xcae3e)[0x7f040877ce3e]
    ./tbs[0x40305e]
    ./tbs[0x402c35]
    /lib/libc.so.6(__libc_start_main+0xfd)[0x7f0407eb3c4d]
    ./tbs[0x401c09]
    
    terminate called after throwing an instance of 'std::runtime_error'
      what():  Failed to open /home/ymmt/eieie
    Abort
    
  • ファイルローカルスコープ
    static は時代遅れ。無名名前空間を使え。
    namespace {
    int local = 3;
    }
  • const ってヘッダに書けるの?
    const 変数は既定でコンパイル単位ローカル。なので書ける。
  • デフォルトコンストラクタのオブジェクトを作る
    以下のコードはダメ。関数になってしまうので。
    Object o();

    正しくはこう。

    Object o;

東京大学の秋入学

日経新聞や各メディアによると、東京大学は海外の大学に合わせて秋入学を検討しているそうな。

高校以下の各学校にまで波及するならばよしとして、もし東京大学単独で秋入学という制度になってしまうようなら、日本の高校生が東京大学に行くメリットは著しく減じるように思う。

能力が飛躍的に伸びる時期に半年あるいは一年のブランクはもったいない。親の視点からすると、そういう状況でわざわざ東京大学にいくのではなくむしろ海外の大学にチャレンジさせたいなと。海外の大学との競争で有利になるどころか、むしろトップレベルの学生の海外流出を招くかも。

ま、それはそれで、日本にはいいことかもしれませんがね。