憂鬱な午後のひととき


5月28日_
日曜日までネットにアクセスできない環境にいます.
5月27日_
引数の型がcharとintの関数は呼び分けられる.
void f(int) {puts("f:int");}
void f(char) {puts("f:char");}
f(1);
f('1');
---
f:int
f:char
class Cを適当に作ってCとcharを引数に持つ関数を作ってももちろん呼び分けられる.
struct C {};
void g(char) {puts("g:char");}
void g(const C&) {puts("g:C");}
g(1);
g(C());
---
g:char
g:C
intかCへの変換オペレータを持つ型を作ってやり, その型を返しても大丈夫.
struct Type {
    operator int() { return 3; }
    operator C() { return C(); }
};

Type getValue() { return Type(); }
int a = getValue(); // (***)
C c = getValue(); // (***)
int a;
a = getValue();
もOK. しかし
C c;
c = getValue(); // VCはOK, gccはNG
だとgcc 4.4.0ではambiguousとなりコンパイルエラー(VC9SP1はOK). ついでに上記g()に渡してもエラー(VC9SP1だとOK).
g(getValue()); // VCはOK, gccはNG
この挙動の差はどちらが正しいのだろう. 最初に示したようにcharとintを区別するならVCのようにgetValue()でも判断してくれてもいいように思う. (***)はgccでもOKなんだし. 実際にはCはstd::stringとしてやりたいんだけど, うまくgccをまるめこむ方法はあるんだろうか.
5月25日_
『C++テンプレートテクニック』(επιστημη, 高橋晶)
読み始める. ぱっと見扱ってる題材がいい感じ. ただレイアウトというかフォントがなんだかな(特に文中のソースコード酷い). もうちょいええのなかったのかな. 最初の方で気づいたところいくつか. p.30のプロパティの実現は抜粋するなら本当に必要な部分にしぼるべき. 散漫でどれが大事か分かりにくい. あとgettterがconst methodじゃないと私はめちゃくちゃ気持ちわるいんだが. 同様にp.38比較関数bool equal(T x, T, y)はずるい. 普通比較関数の引数はconst参照だろうと思う. ついでにこのやり方だと(私もこないだはまったけど)
    char t[] = "c++";
    char q[] = "c++";
    std::cout << equal(t, q) << std::endl;
がfalseになるので例としてよろしくない.
5月23日_
NHK技研公開2009
4年前に行って以来. スーパーハイビジョンで札幌の光景生放送ができていた. 本当に窓の外を見てるみたい. 遅延時間は3秒ほどだそうな. 3Dテレビは結構並んだけどしょぼかった. まだ当分は無理かなあ. ダイナミックレンジ100万:1のスーパーハイビジョン用テレビの表現力は凄かった. めちゃくちゃ艶かしい. 他にはデモ放送にTBSのドラマが使われてたのが意外というか変わったなあと.
5月19日_
SSDのベンチマーク取ってみた(SSD). ちなみに自宅のマシンは(HDD). 4年近く前にしては頑張ってる? 小さいファイルのランダムアクセスは本当に桁違いの性能なんだなあ.
5月18日_
HDDからSSDへの環境移行ツールがうまくSATAを認識せず, かなり四苦八苦したけどなんとか無事終了(同僚の方々, どうもありがとうございました). で, いくつかコンパイルなど試すと体感速度が明らかに違う. これは(値段を無視するなら)お薦め.
5月17日_
HDD整理してたら2001年3月12日(2002年じゃないよ)に中央大学のFACTで初めて新しい形のtraitor tracingの提案をしたときのpdfが出てきた. いろんな意味でかなり恥ずいし(てか酷いTeXだな), 結託攻撃自体は約1.5年後に自分で破ってしまったのだけど(あー, それも論文にしときゃよかった. ISECか何かで発表しただけだったからなー)せっかくなのでupしておく. ラストにopen problemとして「k-wDHAは存在するか?」と反語的に問いかけていた問題は, 当時はもちろんそんなものを仮定するなんて論外なところがあった(Bonehさんらの最初の論文も出てなかったんじゃないかな)けど, 現在も生き残ってq-BBDHIという形になりIEEE P1636.3で提案されるIDベース暗号のprimitiveな仮定の一つになってる(感慨深い).
5月15日_
会社のメインマシンのHDDの調子が怪しいので交換しようかと思ったら「どうせならSSDにしたら?」と言われ, お言葉に甘えて秋葉原へ出陣. 電気街は4~5年振り. 噂に聞いてたメイドさんぽい服来た人がちらほらいてびっくり. それはともかくIntelの160GB購入. お試しに64-bit Windows 7を入れてみたら20分ほどでインストール終了. 動作音も凄い静か(当たり前)で動いてへんのかと思ったぐらい. これは思わず家にも欲しくなってしまった. でも値段がなあ. うーん.
5月13日_
・負担するのは俺らじゃねぇとばかりの補正予算15兆円. 省庁用エコカーやら地デジテレビにも何千億とつぎ込むらしいが, 不況だからというなら, 母子加算廃止を延期するぐらいはしてもえんじゃないか. 自立を促すにしてももうちょい景気がよくなってからで.
・boost.Testを使うと最小テストコードでもvalgrindのチェックで
==20640== Invalid read of size 4
==20640==    at 0x8053522: boost::unit_test::framework_impl::clear()
というような警告が出る(32bit/64bit両方.最適化オプションに関係ない). いろいろ試したら framework_impl::clearの
    if( test_id_2_unit_type( tu.second->p_id ) == tut_suite )
        delete  (test_suite const*)tu.second;
    else
        delete  (test_case const*)tu.second;
    if( test_id_2_unit_type( tu.second->p_id ) == tut_suite )
        delete  (test_suite const*)tu.second;
    else {
        const test_case *p = (test_case const*)tu.second;
        delete p;
    }
のように中間変数に代入してから削除すると大丈夫になった. なんで挙動が異なるねんと思ったが, 実際asm出力を見ても該当する個所で~test_case()を呼び出してからdelete(void*)を呼び出す前に前者だと開放領域にメモリアクセスが一度入るように見える(後者はレジスタに残してる). なんとなく雰囲気は分かるんだけど完全には理解できてない. 簡単なサンプルを書こうとしたけどうまく再現できなかった. どなたか二つの違いが明確になるサンプル教えてくださいまし. てかboost.Test巨大すぎて嫌い. この件も何度か指摘されてる (1, 2 )みたいだし. もう使わない.
5月12日_
ちょっとしたテスト用マクロで
#define TEST_EQUAL(x, y) if ((x) == (y)) ...
みたいなのをつくったのだけど, これではC文字列の比較が意図したものと異なる. で
template<typename T, typename U>
bool compare(const T& lhs, const U& rhs)
{
    return lhs == rhs;
}
を使って
#define TEST_EQUAL(x, y) if (compare(x, y)) ...
にしてconst char*版の特殊化を用意しようとして少しはまった. 上の特殊化は
template<>
bool compare(char *lhs, char *rhs)
{
    return strcmp(lhs, rhs) == 0;
}
じゃなくて
template<>
bool compare(const char *const &lhs, const char *const &rhs);
なんだな. これだと本当にconst char*なものにしかマッチしない. char buf[4]; みたいなのはアウト. で結局特殊化じゃなくてオーバーロードを4つ用意した.
bool compare(const char *lhs, const char *rhs);
bool compare(char *lhs, const char *rhs);
bool compare(const char *lhs, char *rhs);
bool compare(char *lhs, char *rhs);
    const char str1[] = "abc";
    char str2[] = "abc";
    char *str3 = "abc";
    const char *str4 = "abc";

    puts("1");
    compare(str1, str1);
    compare(str1, str2);
    compare(str1, str3);
    compare(str1, str4);

    puts("2");
    compare(str2, str1);
    compare(str2, str2);
    compare(str2, str3);
    compare(str2, str4);

    puts("3");
    compare(str3, str1);
    compare(str3, str2);
    compare(str3, str3);
    compare(str3, str4);

    puts("4");
    compare(str4, str1);
    compare(str4, str2);
    compare(str4, str3);
    compare(str4, str4);
というテストコードでそれぞれどれにマッチするかみたらVCとgccで微妙に違うんだなあ.
    gcc             vc
1   1   1   1     1   1   1   1
ok                ok
        ok        ok      ok
        ok                ok
ok                ok
2   2   2   2     2   2   2   2
    ok            ok  ok  ok
            ok    ok  ok  ok  ok
            ok                ok
    ok            ok  ok
3   3   3   3     3   3   3   3
    ok                ok
            ok        ok      ok
            ok                ok
    ok                ok
4   4   4   4     4   4   4   4
ok                ok
        ok        ok      ok
        ok                ok
ok                ok
どちらも4パターン必要なことには変わりないんだが, ややこしい.
5月8日_
(size_t)-1 とか (size_t)-2 とかを返す関数
506の人が書いてるように正しいです. 『C++プライマー第4版』 p.43にも符号無し型のオブジェクトに負の値を代入することは合法とありますね.
・↓やっぱりvirtualが入ったときVCで間接呼び出しになるのが気に食わない. 試しに
-- a.h --
#ifndef A_H_
#define A_H_

enum Type {
    typeB,
    typeC
};
/* 基底クラス */
struct A {
    A(int type) : type_(type) {}
    const int type_;
    int func(int n) const;
};
#endif
--- b.h ---
#include "a.h"

struct B : public A {
    B() : A(typeB) {}
    int func(int n) const { return n * 2; }
};
--- c.h ---
#include "a.h"

struct C : public A {
    C() : A(typeC) {}
    int func(int n) const { return n + 1; }
};
--- a.cpp ---
#include "a.h"
#include "b.h"
#include "c.h"

/* 仮想関数もどき */
int A::func(int n) const
{
    switch (type_) {
    case typeB:
        return static_cast<const B*>(this)->func(n);
    case typeC:
        return static_cast<const C*>(this)->func(n);
    default:
        __assume(0);
    }
}
--- t.cpp ---
#include <stdio.h>
#include "b.h"
#include "c.h"

int main()
{
    B b;
    C c;
    A* a = &b;
    A* aa = &c;
    printf("sum=%d\n", a->func(10) + aa->func(2));
}
という仮想関数もどきの関数funcを作ってみたら
push    23 ; 計算済み
push    OFFSET "sum=%d"
call    DWORD PTR __imp__printf
add     esp, 8
とまで最適化されてしまった(ちょっと頭おかしいんじゃないかという気もするが). それだったらvirtualでも直接呼び出しするぐらいできるだろうに.
5月7日_
昨日の雑談の中ででたんだけどなんで現行のVCやgccはC++の仮想関数の呼び出しをいつでも間接的にするのだろう.
struct A {
    virtual void func() { }
};
struct B : public A {
    void func() { puts("a"); }
};
int main()
{
    B b;
    A *a = &b;
    a->func();
}
なんてコードはどうみてもputsを呼べばいいように見えるんだが, いちいちvtblを見に行く. __declspec(novtable)なんてのが10年前からあるだからちょっとでもけちろうという考えはあるし, 昨今の最適化能力を見てると難しくはなさそうなんだけど不思議だ. 一筋縄ではいかない理由があるんだろうなあ.
5月6日_
1年振りに(と言っても前回は言葉を交わせなかったが)Kさんと自由が丘で会う. おれおれ言語作りたいとのことなのでできたら是非いじらしてもらおう.
5月5日_
デジカメで撮った数十秒のビデオ(motion JPEG?)がいくらか溜まってきていて, 時間の割に容量が大きいので適当に再圧縮掛けようとよさげなツールを探す. で, 初めてvlc(やffmpeg)の変換コマンドラインオプションを触った(というか変換できるの知らなかった. 単なるプレーヤーだと思ってた). が, 1/4程度にした時点で中途半端に細部ののっぺりさが気になってしまう. 結局HDD買ってくればいいやという結論になってしまった.
5月4日_
Debug Hacks のamazonの書評について
問題はstepとnextが分かりにくいということではないと思う. 初心者向けと謳っているのに「実行」にダブルミーニング(実はそれ以上)を持たせていて, しかも定義にも使ってるのが問題だと思う. hyoshiokさんはデバッグの専門家だけに当たり前すぎてそれに気が付いてらっしゃらないのではなかろうか(もちろん「初心者向け」でなければ普通は問題ないはず. 自分も使ってるし).
自分だったらどう書こう. まず

コンパイル済みバイナリ(プログラム)を起動するとCPUが処理中の状態になり, しばらくすると終了する(あるいはいつまでたっても終わらない)

というのは一般的なので未定義用語として使えると思う(あえて「実行」を避けた). デバッガを使うとプログラムを変更しなくても処理中の状態を任意の場所で一時停止, 再開できる. ステップ実行は, ソースコードの1行に対応する処理が終わるごとに自動的に一時停止し, 再開と一時停止を繰り返す手続きと言えばよさそうだ. 1行に関数を含む場合, nextはその関数を処理したあと次の行に移る前に一時停止, stepはその関数の実装コードの中の1行目で一時停止と言えばどうだろう. 本ではなんでもかんでも「実行」と書いてあるから混乱を招きかねない. たとえば次の箇所は使い分けられる. むしろ実行を使わない方がいいのかも.
  1. p.21 プログラムを実行して → プログラムを起動して
  2. (その下)自動的に実行を一時停止 → 自動的に処理を一時停止
  3. (その下)再実行など → 処理の再開など(ここは本当に再実行(r)じゃなくてcだよね?)
  4. p.22 (下から2行目) 実行を開始します → プログラム(or 処理)を開始します
  5. p.28 (下から2行目) その関数の中も実行したい → その関数の中もステップ実行したい
  6. p.29 (上から2行目) 入り込んでから実行しますが → 入り込んでからステップ実行しますが
  7. p.29 (その下) print_element()関数を実行後 → 関数を処理後
  8. p.29 (その右) 次の行で実行が停止 → 次の行で処理が停止
  9. p.29 (その下)nextiコマンドは関数の中に入って実行しませんが → 関数の中では一時停止(or ステップ実行)しませんが
  10. p.29 (その右)stepiは関数の中に入って実行します → 関数の中で一時停止(or ステップ実行)します
  11. p.29 (下の方) 実行が正常に終了する → 処理(or プログラム)が正常に終了する / 他も同様
  12. p.29 (最後) 再度実行を最初から実行し(悪文) → 再度プログラムを最初から実行し
9や10はfjの教祖様が仰るように「誤記」と思われてもしかたない. 上記リンク先の(2)の「実行」がダブルミーニングなので「明記」になってない.
関係ないけど白地に黒字を思い出してしまった.
5月3日_
手元のサーバの環境でglib-2.9をgcc-4.4.0でコンパイルしようとして半日を費やしてしまった. .cfi_endprocが無いというエラーがあちこちででる. asm周りの文法が微妙に変わったのか何か分からないけど.Sレベルでは直前で改行してないと駄目みたい. ./sysdeps/unix/make-syscalls.shをいじったり, sysdeps.hをいじったりでなんとか進めてたがふとgcc-4.3.3に戻して試したらそこらへんは何も問題なく通った. 4.4は警告も増えてるので(これ自体はいいことやけど)-Werrorついてるのはmakeに失敗する(eg. binutil)こともある. 生成バイナリが遅くなってることもあるみたいだししばらくは4.3.3中心で行こう.
5月2日_
howとwhat
最大値のコードはどっちが楽かで私はwhatが楽な気がすると言ったけどよく考えたら普通はそんな綺麗なケースは少ないので一般的にはhowが楽な気がしてきた(周回遅れの理解). ただ最適化ってのは, いかに与えられた問題の定義域(非自明なことも多い)を満たす限りなく必要条件に近い十分条件で問題を解きやすいものを見つけるかってところだと思ってるので, how + 凄いコンパイラ + α程度ではkinabaさんの発言は難しいんじゃないかなあ.
自分でも言いたいことがよく分かってないのでそのうちきちんと整理する

御意見は光成滋生<herumi@nifty.com>までお願いします