雑な k|m の生態について

その 7 - プログラマの勘ってやつ

2002-07-01

[Mozilla] 上付き文字@メール

ろそろ夏なんですが,大して暑くありません。これが噂のエルニーニョ現象? それともこれから盛大に暑くなるのかな。。。

Mozilla はいくつかの面白い機能を搭載していますが,また今日も見つけましたですよ。メールにて,しかるべき書式で数字を書くとその数字が上付き文字になってくれたりします。

元の書式
2^32
表示
232

「おおっ! これはもしや!」とプログラマの感が働いたところでこんなのも試してみたんですが

2_b
2^{32+1}

むん,これはさすがに無理でした・・・。

他にも ":-)" を顔グラフィックに変換してくれるのは有名です。あと,強調表現は前に紹介した通り。こうしてますます私は Mozilla に萌えてゆくのです。hehehe...

使用中の Mozilla ビルド ID:2002063004

2002-06-30

ウケ狙い

る日,一通のメールがやってきました。

貴社のことは、BrowserCrasherChecker ( cgi.coara.or.jp/cgi-bin/cgiwrap/tkuri/BCC/bcc.cgi? http://some.server.net/ ) で拝見しました。

・・・

・・・そんなところでウケを狙わないで下さい。

リアクションに困ります。

疲労度:463

[ http://some.server.net/ ]
実際には別の URL が書いてありました。

[Mozilla] コーディングスタイル

Mozilla Coding Style Guide

なるほどー,Mozilla は大体こんな感じでコーディングが進められているわけです。Error Handling の項目は特に目からウロコ。私も見習うべきだと思いました。

2002-06-29

[ちょっと改造]

昨日の getExtension() なんだけど,もう少しだけ短くて且つ意味の分かりやすいコードが書けますね。

  const char *getExtension() const {
    if (! extension_) {
      int i, head, tail, pos;
      head = tail = pos = strlen(input_);
      while (pos-- > 0) {
        if (input_[pos] == '.') {
          head = pos + 1;
          break; // found
        }
        if (input_[pos] == '\\' ||
            input_[pos] == '/')
        {
          break; // not found
        }
      }

      extension_ = new char[tail - head + 1];
      for (i=0, pos=head; pos<=tail; ++i, ++pos) {
        extension_[i] = input_[pos];
      }
    }

    return extension_;
  }

これぞリファクタリングの醍醐味。for ループの中では変数 headpos に渡さず,head をそのまま使いまわすのも良いかも知れませんが,変数の意味を考えるた場合はこのようにすべきでしょう。

ちなみに日本語文字コード ( Shift_JIS とか ) の事を考えていないので,日本語を含む拡張子には正常に機能しません。

白眉ハダレダ

渡辺商店でございますっ。

いやその,特別に面白いサイトだーというわけではありませんが ( 失礼 ),その〜,埼玉テレビ,日曜早朝 ( 深夜とも言う ) 0 時からの放送「白眉ハダレダ」という番組と何か関係があるはずなんですが,これまたよく分からない番組なんですが,そこで「何だこの番組は? 気になる方は・・」というテロップと共に示される URL なんですが,うーん,何これ?

なんでこの URL を紹介しちゃったんだろう。少なくともこの雑記のコンセプトとは何ら関係ありません。

無関係度:1286

2002-06-28

[C/C++] mutable 使ってみました

分,一生のうちに使うことは無いだろうなーと思っていた mutable ですが,意外にもあっさりと使いたくなる場面に出会いました。ども,初めましてっす。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

class KFileName {
  const char *input_; // 入力された文字列

  mutable char *extension_; // 拡張子
  mutable char *file_part_; // 拡張子を除いたファイルの部分
  mutable char *file_name_; // パスを除いたファイル名

public:
  KFileName(const char *in): input_(in) {
    extension_ = NULL;
    file_part_ = NULL;
    file_name_ = NULL;
  }

  ~KFileName() {
    free(extension_);
    free(file_part_);
    free(file_name_);
  }

  // 拡張子を得る
  // 拡張子を持たなければ,空文字列へのバッファが返る
  const char *getExtension() const {
    if (! extension_) {
      int i, len, pos, size;
      len = pos = strlen(input_);
      while (pos-- > 0) {
        if (input_[pos] == '.') {
          break; // found
        }
        if (input_[pos] == '\\' ||
            input_[pos] == '/')
        {
          pos = -1; // not found
          break;
        }
      }

      if (pos == -1) { // if not found
        size = 1; // 空文字分のバッファのみ取得
        pos = len;
      }else{
        size = len - pos;
        ++pos;
      }

      extension_ = new char[size];
      for (i=0; i<size; ++i, ++pos) {
        extension_[i] = input_[pos];
      }
    }

    return extension_;
  }

  // ファイル名などを得るメソッドは略
};

int main(void) {
  try {
    puts(KFileName("c:\\windows\\hoge\\index.html").getExtension());
    puts(KFileName("c:\\windows\\hoge\\index.").getExtension());
    puts(KFileName("c:\\windows\\ho.ge\\index").getExtension());
    puts(KFileName("ho.ge\\a.b.txt").getExtension());
  }catch(...) {
    puts("exception.");
  }
  return 0;
}
出力例:
html


txt

ん,なかなかいい感じです。真ん中 2 行は,拡張子を持たないファイル名だったために空文字へのバッファが返って来たわけです。

ここで問題:どうして私は const メンバ関数の中で extension_ をいじりたかったのでしょう? 拡張子くらい,コンストラクタの中で調べればいいのにね。

その理由は,コンストラクタの中から例外を投げたくなかったからです。コンストラクタから例外が投げられると,中途半端なオブジェクトが出来上がってしまいます。これに対応するためにデストラクタの中でも一工夫が必要だったりして,やってられないったらありゃしません。

通常のメンバ変数は,getExtension() const; のような const メンバ関数の中で値を操作することはできません。しかし mutable キーワードを用いることで可能なわけですね。VC++5.0 でもちゃんと使えました。

なお,下のコードは VC++5.0 では通りますが,gcc 2.95 では通りませんでした。

  // ここは,とあるクラスの内部です
  int count;

  void foo () const {
    const_cast<int>(count) = 0;
  }

はて,こういう const_cast の使い方は間違いでしたっけ・・ mutable のおかげで,もうどうでも良いことではありますが・・。

疲労度:22

2002-06-27

[2ch]ひろゆきさん,400 万円賠償命令

決に関して,支持する意見と疑問視する意見の双方が出揃ったかと思われます。2ch では当然ひろゆきさん擁護派が多数。対して /.J ではそれぞれの立場で意見が出ている模様です。

個人的な感情としては「400 万円て額はどうなのかな」と。大手マスコミが個人を誹謗中傷したとして課せられる賠償金額でさえ,せいぜい 50万円〜200万円なのに ── もっとも,この額は世界的には「異常に低い」らしくて,国内でも批判の声が上がっているほどですが。

その一方,これはいろいろな要素を見て動的に決定されるべき金額であって,賠償額の相場が一律に 50 万円〜 200 万円と決まっているわけではないとの指摘。そういう意味で「400 万円」は妥当な数字とのこと。むーん,難しいです。

しかししかし 2ch の過去ログを読む限り,病院側の人はとにかく「削除しろ」の一点張りだった模様。どのスレッドかさえも示していないように見えます。例えば,もし現在の私が突然メールで「漏まえ,酷い野郎だな。とっとと削除せいや」と言われても,困る以外には何もする事ができないでしょう。せっかく `URL' という,ネット上の任意のリソースを他と混同することなく指し示すことのできる `便利な呪文' があるんだから,せめてその URL を示して削除依頼を行う・・・という最低限の礼儀は,まだまだ市民権を得ていないのでしょうか。

「怪しいファイルを実行してはいけない」でさえまだ常識とは言えないらしいから,無理もないのかなあ? 知らないうちにウィルスを拡散させたとしても,その人はあくまでも「善意の第三者」いやむしろ「サイバーテロの被害者」なーんてな風潮ですもんね・・・。

しまった。こういう事件がある度に,今日書こうとしていたプログラミングのネタを書く時間と気力がなくなってしまうのです。どっとはらい。

[ 常識とは言えない ]
・・・らしいんだけど,この辺のリソースを示せないのでスッキリしません・・・そもそも常識って何よ (`Д´)
[ どっとはらい ]
「めでたし,めでたし」の意味。

ちょっとだけ訂正

昨日の雑記で「FAX のようなハードウェア」と書いてますが,全然違います。正しくは OCR でした。

・・・あれれ? なんで OCR に TIFF を吐かせるの? テキストファイルじゃなくて?

[ OCR ]
`R' は,デバイスを指すなら "Reader",処理を指すなら "Recognition" です。

2002-06-26

8649.H

ろしく,かな? そう読めますよね。

そうじゃなくて,今 TIFF を扱った仕事やってます。FAX のような ( ? ) ハードウェアが吐き出す,無圧縮で 2 色の TIFF ver6。昔から Towns-TIFF で少しだけ慣れていたので,今回新しくデコーダを書くのにも 1, 2 日で終わりそうです。・・プリンタを扱ったり DB を扱ったり,一体私は何をやってるんだろう?

まともな仕様書を読みながらのコーディングは久しぶりです。その前に出会ったまともな仕様書といえば,Deflate 圧縮をするために読んだ RFC。しかも`趣味グラム'の範疇。仕事で読む仕様書の方がまともじゃないってのは,社会人 2 年生の私には少々意外でした。少々ね。

表題の 8649.H は,そこで出会ったちょっと不思議な 16 進数字。仕事で扱う予定の TIFF ファイルのサンプルを相手先から送ってもらったので,さっそくタグを解析してみました。

8649.H - unknown

なんじゃこのタグは? Google で調べたところ,世界中のどのページでも「よくわからないタグ」として頭を抱えていました。一説によると DPI を保存しているものらしい ( KRN.さん ) とか,"Photoshop image resources information" と名前がついていたりするらしいとか,GIMP では異常な振る舞いを見せてくれるだとか。

とりあえず判っているのは,Adobe 社が独自に拡張したものらしいということ。む,確かに他にも Adobe の拡張タグがちらほら・・・あれれ?

送ってくれたこのサンプル TIFF ファイルは何? "Photoshop" てどういうことよ? FAX のようなハードウェアが吐き出す TIFF じゃなかったの?

と相手方にツッコミを入れるのは後日ということで。

謎度:356

[ DB を扱ったり ]
ちなみに DB を扱う仕事は前の仕事の名残です。

2002-06-25

[WinAPI] FormatMessage() で注意すること

Windows ネタばっかり。というのも,私には Linux を導入できない致命的な理由があるのです。・・・だって Windows がないと TV アプリケーションが動かないもん。あ,エミュレータっていう手もあるのかな? でもやっぱし怖いです。。。動作保証外だし。。。

さて,次のコードは GetLastError() で得たエラーコードからエラーメッセージを得る関数。MSDN にも載っているものですね。

LPTSTR getErrorMessage(DWORD err){
  LPVOID lpMsgBuf;

  ::FormatMessage(
      FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL,
      err,
      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
      (LPTSTR) &lpMsgBuf,
      0,
      NULL);

  return (LPTSTR)lpMsgBuf;
}

バッファの解放には LocalFree() を使うとよいでしょう。ただし Win32 では Global も Local も区別されないとのことなので,将来は LocalFree() がサポートされなくなる可能性もあることに注意。緩衝関数を用意しておいた方が無難です。

void releaseErrorMessage(LPTSTR buf) {
  ::LocalFree(buf);
}

これらの関数にいろんな値を入力して遊んでいると,ある出来事に遭遇します。それはズバリ,バッファに NULL が返って来る場合がある事。さすがの Microsoft も 40 億ものエラーメッセージを定義するのは困難だったようです。・・あたりまえじゃっ。

というわけで,実際に使う場合は次のようにすべきかと。

int main(int argc, char *argv[]) {
  LPTSTR msg = (argc == 2) ? getErrorMessage(atoi(argv[1])) : NULL;

  // バッファが NULL かどうかを検査するべき
  puts(msg ? msg : "Input valid error code.");

  releaseErrorMessage(msg);
  return 0;
}

うーん,LocalFree() に NULL を渡してもいいのかな? 多分,問題はないでしょうが。

疲労度;18

[ 致命的な理由 ]
あと,今までなぜか運に恵まれず,ことごとく Linux のインストールに失敗してきたという経験も。
[ 40 億 ]
DWORD 型は 0〜40億くらいまでの値を表現できます。

2002-06-24

[VCL] 言ってるそばから!

日の雑記でマルチスレッドでの realloc の危険性を大々的に書いてみたんですが,気になってとりあえず VCL の TServerSocket クラスのソースを覗いてみました。

これもプログラマの勘ですか・・・案の定,無防備な realloc をやっちゃっています。

問題なのは TServerWinSocket::GetConnections。だいたいこんなコードになっているはずです。

// 注意:C++ に似てるけど
// とりあえず仮想コードのつもり (著作権とか怖いんで)
TServerClientWinSocket
  TServerWinSocket::GetConnections(int index)
{
	return FConnections[index];
}

この FConnectionsTList クラス ( の派生物だったっけ? )。Add メソッドを備えていて,バッファが足りなくなると Grow メソッドでバッファを増やします。そしてその中には魅惑の realloc が。TServerSocket はクライアントを accept すると TServerWinSocket::AddClient を呼び出し,FConnections.Add(client) を実行します。その瞬間にたまたま別スレッドで FConnections[index] てな風に走査していると,もしかすると・・もしかすると・・問題が起きる・・かも・・知れません。

幸いにも VCL 設計者はちゃんと考えていました。TServerWinSocketFListLock というクリティカルセクションオブジェクトを持っています。これは無論,FConnections のための排他制御オブジェクト。他のメソッドがやっているように,TServerWinSocket::GetConnectionsFListLock.Enter()FListLock.Leave() で囲んであげればよいだけです。なお,AddClient メソッドには既にそのコードが埋め込まれているので大丈夫。

// 仮想コードのつもり
TServerClientWinSocket
  TServerWinSocket::GetConnections(int index)
{
	FListLock.Enter();
	try {
		return FConnections[index];
	} finally {
		FListLock.Leave();
	}
}

そう,こんな感じでやさしく Lock してあげます。間違って Rock しようとすると,実際に Rock されるのはあなたの命──プログラマ生命──かも知れません。

ぐはっ! なぜだか判らないけど脱力したんで,寝ますっ!

脱力度:342

2002-06-23

勘が冴えるですよ

「プログラマの勘」・・あー,なんだかこのフレーズが妙に気に入ってしまいました。雑記でもしばらくあちこちで登場すると思うので,夜露氏区。

言葉の意味は・・・例えば,ネットラジオを聞いている時に CPU 占有率やネットワークの状況を眺めながら,ある瞬間に「次,音が飛ぶかも?」と感じる事。それから任意のアプリケーションの,WM_PAINT に対する反応の極わずかな遅延を感じて「ブルースクリーンっ!?」と叫ぶ事。さらに電源投入時,電源スイッチの手応えで「今日は 6 時間 22 分後に一度リブートした方が良さそうだ。さもなければ Mozilla が 0x00000005 のメモリを参照,"read" になる事が出来ず,しかる後にシステムが不安定になるだろう」と判断できれば,神と呼ばれる日も近いですとも。

[Socket] リストの持ち方 [杞憂?]

TCP/IP サーバを作る際,accept したクライアントからの接続をしばらく保持したまま次の accept を続けなければなりません。つまり accept 作業とクライアントからの recv 作業は別スレッドになるわけです。

例えば VCL の TServerSocket クラスは,個別のクライアントを Socket プロパティ ( TServerWinSocket クラス ) が持つ Connections 配列プロパティ ( TServerClientWinSocket クラス) で保持しています。ということで,私もリストみたいなコンテナクラスを考えてみます。

例えば私がリストクラス "template<typename T> class CList" を作った場合,内部の T *items_ メンバがバッファを保持するでしょう。まず最初に少しバッファを持っていて,さらにバッファが足りなくなれば realloc() でバッファを増やします。これ最強。

#define DELTA 10
template<typename T>
class CList {
  T *items_;
  int capacity_;
  int count_;

protected:
  bool expand_() {
    T *temp = (T *)realloc(items_,
                 sizeof(T) * (capacity_ + DELTA));

    if (! temp)
      return false; // バッファ再割り当てに失敗

    items_ = temp;
    capacity_ += DELTA;

    return true;
  }

public:
  CList() {
    items_ = NULL;
    count_ = 0;
    capacity_ = 0;
    expand_();
  }

  virtual ~CList() {
    free(items_);
  }

  int add(T obj) {

    // バッファが足りなくなれば再割り当て
    if (capacity_ <= count_)
      if (! expand_())
        return -1;

    items_[count_++] = obj;
    return count_-1;
  }
};

さて CServerSocket の方にとりかかります。( CThread のコードは省略するとして ) 私の CServerSocketopen() によるサービス開始後,accept と recv 用の 2 種類のスレッドが必要です。

// recv 時のコールバック関数を登録
void CServerSocket::setRecvEvent(CServerEventType ev) {
  recv_event_ = ev;
}

// サービスを開始する
bool CServerSocket::open(unsigned short port) {
  // クライアント接続リストを生成
  connections = new CList<CSocket *>();

  // サービス開始
  socket_ = new CSocket(NULL, port);
  socket_->listen();

  // accept を行うスレッドを起動
  accept_thread_ = new CAcceptThread(this);

  return true;
}

// 奇怪な運命を経て,CAcceptThread は次のメソッドを実行
void CServerSocket::doAccept() {
  CSocket *client;

  while (! accept_thread_->terminated()) {
    client = socket_->accept();
    if (! client) break;

    // accept 成功,リストに加える
    connections_->add(client);

    // recv を行うスレッドを起動 ( 自力で delete を行う )
    new CReceiveThread(this, client);
  }
}

// 奇怪な運命の末に,CReceiveThread は次のメソッドを実行
void CServerSocket::doReceive(
      CReceiveThread * receive_thread,
      CSocket        * client
){
  while (! receive_thread->terminated()) {
    // データが送られてくるのを待つ / 切断されたら break
    r = client->waitReceivable();
    if (r == 0) break;

    // コールバック関数を呼び出す
    if (recv_event_) recv_event_(this, client);
  }
}

コードが長っ。こんなのは雑記で扱えるネタじゃなかったなーと後悔しつつ,実際に CServerSocket を使ってみます。

#include <stdio.h>
#include <conio.h>
#include "socket.h"

// クライアントからデータを受け取った時,
// そのクライアント以外のクライアント全員に再送信
void serverReceive(
      CServerSocket *self,
      CSocket *client
){
  char buf[256];
  int i, r;

  r = client->recv(buf, 255);
  if (r == 0) return;

  buf[r] = '\0';
  printf("%s", buf); // 表示

  // クライアントに再送信
  for (i=0; i<self->connections->getCount(); ++i) {
    CSocket *sock = self->connections->getItem(i);
    if (sock != client)
      sock->send(buf, r);
  }
}

int main(int argc, char *argv[]) {
  CServerSocket *ss;
  WSAData _wsa_data;
  ::WSAStartup( MAKEWORD(2,0), &_wsa_data );

  ss = new CServerSocket();
  ss->setRecvEvent(serverReceive);
  ss->open(7001);

  _getch();

  delete ss

  ::WSACleanup();
  return 0;
}

せっかくここまで書いたのに,実はこの方法はかなり低い確率で問題が発生する事に気付いたのです。

[ 別スレッドになる ]
無論,このあたりは設計にもよるでしょう。accept したら別の accept 待ちスレッドを立て,自分はそのままクライアントからの recv を処理する,てのも綺麗なやり方。
[ コンテナクラス ]
意地を張らずに STL 使え (`Д´)

大山鳴動して・・・結論はそれかいっ!

要するに,マルチスレッドなコードで安易に realloc() をしちゃダメってこと。あんなに大量にコードを書いておきながら,今日の雑記で言いたかったのはそれだけ

上の例では,サーバがクライアントからデータを受け取った時に connections というリストオブジェクトを全て走査します。CList::getItem() は ( そういえば上のコードでは明記してなかったけど ) CList::items_[] に直接アクセスするもの。その走査中に新たなクライアントから accept が起こり,そして items_realloc() が起こったら・・・問題はあるのかなあ?

[ recv スレッド ] [ accept スレッド ]
getItem(i) メソッド呼び出し
        :
引数 i の値と items_ ポインタの値を
レジスタにロード
  items_ の値 .. 0x10
  i の値      .. 0
        :
        :
ロードした値を計算する
  sizeof(CSocket*) * 0 = 0
  0x10 + 0 = 0x10
        :
        :
        :
0x10 には CSocket オブジェクトの
ポインタを示す値が・・・
・・・あれっ!?


クライアントから accept
         :
         :
         :
         :
items_ を realloc する
  items_ の値 .. 0x20
この時点で 0x10 には何もないっ!

つまり,とんでもなく低い確率で致命的なエラーも起こりえる,というわけです。確率こそ低いものの,プログラミング時にはここまで考えなければならないので大変です。

解決法としては,accept するたびに同期を取るだとか? でも面倒くさいんで,やっぱり "リンクリスト" が最強ですか。

疲労度:16493

[ (テーブル) ]
今回の雑記はテーブルを使っているため,一部の UA ではわけが分からなくなってるかも・・・
[ 同期 ]
VCLの "TThread" では,スレッドに特別なイベントを投げることで同期を取るという手段を提供しています。他には クリティカルセクションを用いるとかですか。
[ (↑) VCLの "TThread" では・・ ]
TThread は Visual じゃないという罠。しかも継承させないと使えないので,Component とさえ言えるかどうか。
Written by kuri|minima(tkuri@fat.coara.or.jp) - all rights reserved.(warai
このリソースの位置情報は http://www.coara.or.jp/%7etkuri/D/007.htm で安定しています。coara - the king of network!