このタイトルのつけ方はちょっとマズいかも? だって,どんな忘れ物でも大抵は「意外」ですもんね。・・まぁいっかー。
今日ミスっちゃったのは,こんな感じ。
class CSocket {
SOCKET descriptor_;
sockaddr_in addr_;
public:
CSocket();
~CSocket();
bool open();
bool close();
bool getIsActive();
const char *setHost(const char *h);
unsigned short setPort(unsigned short p);
const char *getHost() const;
unsigned short getPort() const;
bool getIsActive() const;
};
CSocket::CSocket(){ (略) }
CSocket::~CSocket(){ (略) }
bool CSocket::open() { (略) }
bool CSocket::close(){ (略) }
const char *CSocket::setHost(const char *h) { (略) }
unsigned short CSocket::setPort(unsigned short p){ (略) }
const char *CSocket::getHost() const { (略) }
unsigned short CSocket::getPort() const { (略) }
bool CSocket::getIsActive() const { (略) }
そしてコンパイルすると,こう ( VC++ 5.0 ) :
""public: bool __thiscall CSocket::getIsActive(void)" (?getIsActive@CSocket@@QAE_NXZ)" は未解決です
通常,この場合は「宣言しているくせに実装していない」ようなパターンでみられるリンクエラー。しかし今回の例はちょっと違うようです。なんせ,実装されていないと言われた getIsActive() メソッドはちゃんと実装しているわけですから。
これは気付きさえすれば単純なミスでした ( ほとんどのミスはそうだけど ) 。つまり私はヘッダファイルでうっかり `bool getIsActive()' と `bool getIsActive() const' を宣言していたわけです。両者は ( 多分 ) オーバーロードの関係とみなされ,何ら問題なくコンパイルには成功してしまいました。しかし `bool getIsActive() const' は実装したものの `bool getIsActive()' は実装されていないため,リンカがエラーを検出した,というのが真相。
引数の違いでオーバーロード関係が成立するのは知っていましたが,というより C++ の基本ですが,まさか const メソッドか否かでもオーバーロード関係が成り立ってしまうとは。いよいよ,ちゃんとした C++ の本を買わないといけないっすかね・・・。
うっかり度:436
C 言語では,配列をコピーするならば memcpy とか memmove 関数を使います。基本中の基本。
memcpy(s, ct, n); // s <- ct
memmove(s, ct, n); // s <- ct
ただし C は基本的に配列の長さについて「無防備」なため,n の値が実際に許される値を超えていたりしたらもう目もあてられません。世界中のソフトウェアのバグの半数はこのあたりだったりして?
というわけで,こんなコードを書いてみました。
#include <stdio.h>
#include <string.h>
typedef struct _array {
char buffer[100];
} Array;
int main(void) {
Array str1, str2;
strcpy(str1.buffer, "foobar");
str2 = str1; // コピー(ただの代入演算)
puts(str2.buffer);
return 0;
}
コンパイル,リンク・・・そして実行・・・。・・うわっ,なんか正常に動いたっぽいしっ!!
構造体の代入演算子は ( C++ では operator= がオーバーライドされなければ ) 内部のメンバ変数を全てコピーします。これを使っている場面は全然見かけないけども,便利な場合は便利な機能です。
しかし・・ハッキリとは言えないんだけども,これはとんでもないマヌケコードの予感がしました。具体的にどんな弊害をもたらすかはよく分かりませんが,いわゆるプログラマの勘ってやつです。とにかく,こんなコードは書いちゃだめです。
・・(ふと我にかえって)・・この激しい拒絶反応は何なんだろう? とりあえずこんなコードを書く暇があれば,RFC の一つでも読破しようと思いました。
疲労度:67
memmove ]memcpy とは区別されるとのこと。まじっすよ。基本的な事項なのに,今日初めて知りました。私のプログラマーとしてのダメさ加減の凄さは異常。・・・会社が小さい & 若いってことで経験が豊富じゃないし,その分コードのストックも少ないしね。というより,皆無だし。
普通,SQL の INSERT 文と言えば次の形を想像するじゃないスか。
INSERT INTO table_name VALUES (
1, 0, 0, NULL, '名前', 23, '2002-06-20', NULL
);
フィールド名を指定していないため,DB のフィールドの順序を替えただけでこのコードはアウト。私,今日まではそれが当たり前だと思っていました。
しかし相手先がなぜか「INSERT 文のフィールド名を明示しろ」とやかましいのです。INSERT でフィールド名を明示? UPDATE で汚い map と join を使ってフィールド名を隠蔽していた事があったけど,そのこと? ていうかもう修正したはずだし? ・・・あ,もしかしたら??
いわゆる `プログラマの勘' ですか。すぐに Google に Go。・・・瞬時に見つけたですよ,相手先が何を言いたかったのか。
INSERT INTO table_name(
PID, PNUM1, PNUM2, PSTR, PNAME, PAGE, PADD, PUPD
) VALUES (
1, 0, 0, NULL, '名前', 23, '2002-06-20', NULL
);
テーブル名の後ろにフィールド名を列挙してやることで,DB に定義されたフィールド名の順序に関わらず,対応する値を正しいフィールドに挿入してやることができます。考えてみれば,こんな機能がついているのは当然のような気がしました。SQL 初心者の私自身でさえ,こんな機能が欲しいと思っていたわけですから。
SQL,侮りがたしっ!
疲労度:46
<ぐち>
やっとこさ,前の仕事のバグ報告と仕様変更,追加要求のメールが届いた模様です。リリース後に一件もバグ報告がないのは,プログラマにとって精神衛生的に非常によろしくありません。・・ただ,確か検収期間は 7 営業日だと聞いたような気が。もう 14 営業日も経ってるという事実はタブーですか。
といっても,全部が機能の変更や追加ばかり。サブルーチン単位で何度もテストしていたおかげで,目立ったバグはなかったようです。
・・問題はそこなんですよ。その機能追加要求に,オトナはどう応えるのか。
部長「とりあえず拒絶しておいて,どうしても〜と言われたら仕方なくやってあげるのが原則です。」
お金が絡んでいる以上きちんと「ケジメ」なるものを着けておかなければなりません。相手先は,提出されたコードについて要求した仕様を満たしていることを確認し,その上で機能の追加を要求していると解釈されます。この場合,追加料金を頂くべきであることは明白。少なくとも無償で要求をのんではいけないのです。そうでないと最悪,業界全体の商品の単価を落としてしまう場合もあり得ます。
いやや〜〜〜〜〜〜〜っっ
いやや,いやや,そんなのいややーっ。つまり相手先はアレで満足しなかったのです。機能の追加だってほんのわずか。めっちゃくちゃ対応してあげたいっ。いいじゃんっ。私は常に最高の製品を作っていきたいっ! 欲求不満にも程があるわっ!! うえーーーーーん (ノД;)
・・とまあ,ダダをこねてたらお金が貰えなくなるわけで。いろんなモノを我慢しながら,私は一歩ずつ大人への階段を上らないといけないようです。はあ,大人になりたくないなあ・・・
</ぐち>
年齢:もうすぐ 24 歳
もういっちょ HTTP ネタです。サイトの巡回中,変わった HTTP ステータスコードを見つけたんですよ。
Bandwidth Limit Exceeded
The server is temporarily unable to service your request due to the site owner reaching his/her bandwidth limit. Please try again later.
調べるのにちょっと手間取りましたが,どうも apache 用のモジュール mod_bandwidth を使って転送量制限を施すとこの画面を見ることが出来る模様。RFC2616 には特に記述されていないんで,ちょっとびっくりです。
さらに少し調べてみたんですが,結構いろんなプロトコルでステータスコードは拡張されている模様ですね。例えば 5xx 系のエラーコードにこんなのが定義されてます。
これは WebDAV で定義されたステータスコード。WebDAV は XML とかを使った次世代プロトコルらしくて,かっちょいいですね。お次は CATP というちょっとマイナー気味 ( 失礼 ) なプロトコルで定義されているステータスコード群の一つ。
あれれ,確か 510 は・・・
RFC 2774 "An HTTP Extension Framework" でも同じものが定義されていました。たまにはこういう事もあるんですねー・・・。
HTTP をベースに独自拡張されたプロトコルは世界中にあるわけで,それぞれのステータスコードを調べてみるのも楽しそうかも。とりあえず,暇つぶしの材料がまた一つ増えました。
疲労度:13
近年まれに見る大チョンボをやらかしたですよ。すいません,最近,ブラクラチェッカーがまれに重い時があったかと思います。
昔は私も大変な厨房でして,やっとこさ「ソケット」なるものに触れることが出来た頃のお話。今でこそ RFC を読みながら大抵のプロトコルが理解できますが,厨房の私は当然,ソケットをオープンして connect するのがやっと。HTTP のリクエストの投げ方なんぞ他のソースから拝借しちゃったですよ。
そして試しに www.goo.ne.jp にリクエスト。ちゃんと最後までデータは取得できているはずなのに,なぜかサーバの方からソケットを切断してくれない模様。recv() から EOF が返ってきません。ぬぬぬ・・・。
いあ,今考えると恐ろしいっすね。Connection:close でリクエストしていませんでした:いわゆる「持続的接続」ってやつですか。こちらから切断してあげないと切断できるわけがありません。だからシグナルを用いた制限時間 20 秒がすぎるまで,じっと待ってたというわけです。最初はブラクラチェッカーの利用者も多くなかったんで,それでもあまり問題はなかったんですよね・・・。
しかし事態は一変,万単位でアクセスが来るようになりました。運良く Connection: close を知り,スクリプトに埋め込んだのも丁度その頃。さらにスクリプトを分割して CGI の使える無料ホスティングサービスを渡り歩いたり,東京に出てきてからは internet jah 様に出逢ったり,なんとかやりくりできてました。
そしてスクリプトを大々的に弄ったのは去年の暮れ。きっとその時に違いありません。うっかり Connection:close を削除してしまったのは。
6 月 16 日,「固定キャッシュ検索室」で検索できなかったために coara サーバにまで持ち込まれた件数は 10922 件。そのうち重複やイタズラを除き,実質的な稼動回数はおよそ 6500 件。13 秒に 1 回はソケットで外部データを取得している計算です。その間中,Transfer-encoding:chunked なデータに遭遇するたびに coara サーバの CPU 時間を食いつぶしていましたとさ・・・。
・・・ごめんなさい。
あちこちのサイトを巡回してると,結構「確信犯」って言葉に出くわすんですよね。
特に多いのは例の /.J。10 ストーリーに 1 回くらいの頻度で出てくる気がしてます ( 別に数えてるわけじゃないんだけど ) 。しかし読んでいるとどうも違和感が出てきて,ふと「もしかしたら,この部分は“すべてを計算した上での犯行”な意味で使ってる?」と戸惑うこと多数。
ていうか分かるからいいんですけどね。
私が危惧するのは,重要な文書における表現のゆらぎがとんでもない事故を起こすのではないかと。例の「確信犯」も,本来とはおよそ正反対の意味で用いられてしまっています。誰かが間違った行いをしでかして「ああ,確信犯だったんだね」と発言した場合,本来「悪気はなかったんだね」と捕らえられるはずが「悪意マンマンだったのな」と捕らえられたりして一気に険悪なムードに。特にデリケートな国家間の文書のどこかでこんな誤解が発生すると,もう目もあてられません。数千人単位で死者が出ます。
他によく例を挙げられるのは「須らく」とか「情けは人のためならず」くらいですか。
最近では「須らく」を「全て」の意味で使ったり「情けは人の──」を「無闇な親切は,その人のためにならない」の意味で使ったりする人が多いらしくて,なかなか混乱が起きてる模様。いっそのこと,使用禁止令を出しちゃうとか!?
言葉は時代に従って変遷するものらしいんで「日本語が乱れている!」とか抜かすつもりはないですが・・・なー・・・。
そうそう,「とても」は本来「──ない」と同時に用いられるものだったとか聞きました。「とても完成しそうにない」ですね。今じゃ普通に「とても美しい」とか使ってますが。
涼しさ度:80
やっぱしイイですよね,この手のジョークは。日本独特の「ボケ・ツッコミ」もいいですが,こちらはまた違った雰囲気。シンプルさというか,美しさというか,スマートさが漂います。ツッコミは不要。意味がわからないギャグは勉強あるのみです。
余談:Perl で自分の足を撃つには? 引き金を引く方法は何通りもあります。引き金を引いてから弾を込めても構いません。狙ってもいないのにうまく足に当たることもあります。use strict; によりいい加減な方法を抑制する事もできます。そして足を撃った! 痛い! かなり痛い! しかし足に穴は空きません。
さらにもう一つ;Java で足を撃つには・・・無理だっ! ( 銃に仕掛けられた何重もの安全装置を見よ )
んにゃ,今日ヤっちゃったのは私ではなくて,部長です。生粋の Pascal-er。
彼らはほんの 2m 向こうに居るのに,私自身の仕事が忙しかったりするんで,彼らが今ナニをやっているかに興味を持つことができません。ある人はショッピングサイトを作ってるらしいし,部長もたまに別のコトをやってるらしいし,私は帳票印刷の仕事中です。
そんな時でした。
部長「これ分かりますか〜?」
画面には Pascal のコードがずらり。話によると,レコード ( C でいうところの構造体 ) 1 件に値を代入したところでファイル変数 ( C でいうところのファイルディスクリプタ ) が壊れてしまうらしく,その周辺で異常な振る舞いを見せてくれるとか。
type TRecord = record
...( 略,かなり大きなレコード )
end;
var
Records : array[1..100] of TRecord;
procedure Func;
begin
for i=0 to Foo-1 do begin
Records[i].Hoge := Hoge; // 適当
...( 略,たくさん代入 )
end;
end;
あ,タイトルで既にネタバレ風味でしたが・・・まあいいや。Records はみての通り TRecord の配列です。下のプロシージャの for ループで次々に値をセットしていきます。
大抵このような場合,配列の添え字の値をオーバーさせていたりするんですよね。C だと char a[100]; と宣言しておいて a[100] にアクセスしちゃったり。上のコードもきっと Foo - 1 の値が悪さをしているに違いありません。
私「ここのループがアレなんじゃないスか?」
部長「んー,ここはちゃんと 0 から 3 までなんですよ。確認してます。」
私「( 思わせぶりに ) 0 から。。。うーん。。。」
部長「ええ,ゼロから。。。あっ!!」
その瞬間,私も気付きましたですよ。配列 Records の添え字が 1 から始まっていることに。
C や C++,Java なんかは,配列の添え字は 0 から始めるのが普通です。FORTRAN は 1 からでしたっけ? ObjectPascal や Perl は自由に設定できますが,特に Perl は 0 から始めることが推奨されています。つまり特殊変数 $[ の値はもはやいじってくれるな,とね。しかし Pascal の世界では,どうも "0" 派と "1" 派が混在している模様。しかも悪いことに ( Borland 製 ObjectPascal ではきちんと警告を出してくれるらしいですが ) 部長が使っていたフリーのコンパイラでは警告さえ出してくれなかったということで,悪運が重なってしまったのです。
配列の添え字の問題について,また一つおりこうになりました。ehehe...
教訓:たまには神に祈れ
ただいまプリンタとの格闘中であります。ということで,実際に印刷を行うコードを組んでみました。単に DC への描画に際して StartDoc(),StartPage() と EndPage(),EndDoc() で挟んでやるだけなんで,コードは割愛。
私「ぁ印刷っっとくらぁ・・」
プリンタ「ンジー・・・ガショバリジョバリバジョリバリバリッ」
私「のはぁっ!?」
紙がグシャグシャになりました。なんで? どないやねん? MSWord なんかでは正常に印刷が出来るわけで,これは明らかに私が書いたコードの問題です。
とりあえず気を取り直して,試しに Delphi の TPrinter を使って同様のコードを組んでみました。んで,実行・・・
プリンタ「ンジー・・・ガショバリジョバリバジョリバリバリッ」
またそのパターンかよっ!? しかも ( コードの要所要所でエラーチェックを行っていたわけですが ) BeginDoc の時点で既にエラーが起きています。GetLastError で得たエラー番号は 122,内容は「システム コールに渡されるデータ領域が小さすぎます」。
もうわけわかりません。TPrinter の扱い方を間違っているのかも知れないし・・・VCL にだって当然バグはあり得るわけです。ソースが全て提供されていると言っても,納期に追われる商業プログラマにとってはタチの悪いブラックボックスにしか見えません。TPrinter の扱い方を調べ挙げてマスターするのと WinAPI を自分でいじるのとではどちらが生産的なのか,小一時間悩みました。
とりあえず再び気を取り直して,VC++ でもう一度 WinAPI から印刷してみようかな・・。そこでグシャグシャな紙を見てふと気付きました,紙の隅の方に,印刷したかった文字がわずかに印刷されているんですよね。これはもしかしたら TextOut(hdc, x, y, str, strlen(str)); の x,y がいけなかったとか?
座標をもう少し大きめに ( 文字が真ん中よりに印刷されることを期待 ) して,実行・・・。無事に印刷されました♪
つまり TextOut() の印字結果が印刷可能範囲か何かを越えてしまうと,プリンタによっては紙をグシャグシャにしてしまうわけですね。紙のサイズや印刷可能範囲を得るために,事前にちゃんと GetDeviceCaps() で各種設定を調べてやる必要があるようです。
なんか,面倒くさー・・。
疲労度:56
原因など私にはさっぱり想像できませんが,2〜3 週間に 1 日ほど,ネットが異常に重くなる事があります。メールが 80 通ほど着ているはずなのに ( Mozilla が報告してくれる ) いざ読み出そうとすると "sending login information..." のまま音信不通となってしまいます。web を徘徊しても 50% の確率で "Operation timed out" です。FTP を繋げることはまず不可能。
そして,今日がまさにその日。果たして私はこの雑記を無事に更新することが出来るのでしょうか・・・。
Windows はまだまだ TCP/IP 通信の歴史が浅いため,その周辺でヘンなトラブルもあったりします。ちょっと便利なことをしてくれているがために,いざという時に web に接続できなかったりね。
gethostbyname() 関数は一般に hosts ファイルや DNS や,なんか色んな仕組みを駆使して名前解決を試みます。「DNS キャッシュ」という仕組みもあって,きっと数日以内に接続したドメインと IP をローカルに憶えておくとか,そんな感じっすか?
会社などで実験的に web サーバを構築,運用していたりすると,IP を替えたとたんに gethostbyname() の名前解決に失敗してしまうことがあります。これは DNS キャッシュが悪さをしているんですね。"キャッシュの有効期限が切れるまで待つ" というのも一つの方法ですが,それはちょっと悠長すぎるかと。
Windows ではレジストリの値を変更することで,DNS から名前を解決し直すことが出来ます。これは ZDNET の Windows 2000 Guide で少し触れられているものです。
レジストリ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\VxD\MSTCP\ServiceProvider の DNSPriority の値を小さくします。1 とか。
レジストリ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ServiceProvider の DNSPriority の値を小さくします。
そして再起動。改めて gethostbyname() をし直すと,新しい IP が DNS からローカルなキャッシュに反映されます。ただしこのままでは常に DNS へ問い合わせに行ってしまうので,さっきいじったレジストリを元に戻しておくことも忘れずに。
疲労度:37
タイトルのまんまですが,プリンタで遊びます。お仕事でね。となると Windows よりの話ばかりになってしまいますが,でもそこが Microsoft が頑張った部分なんで・・・。
意外だったのは,使用可能なプリンタ名を列挙するのがわりと簡単なように見えたということ。無論これからいくつかの罠に出会うわけで,どう寝違えるか分かりませんけどねー。
とりあえず今日書いたのはこんなコード。単純に使用可能なプリンタ名を列挙,出力するだけです。
DWORD enumPrintersName() {
BYTE *buffer;
DWORD needed, returned;
EnumPrinters(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
NULL,
4,
NULL,
0,
&needed,
&returned );
buffer = (BYTE*)malloc(needed);
EnumPrinters(
PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
NULL,
4,
buffer,
needed,
&needed,
&returned );
for (DWORD i=0; i<returned; ++i) {
puts( ((PRINTER_INFO_4 *)buffer)[i].pPrinterName );
}
free(buffer);
return returned;
}
WinNT,Win2000 専用ですが,第 1 引数 Flags は PRINTER_ENUM_LOCAL のみ,第 3 引数 Level を 5 にして,PRINTER_INFO_4 構造体へのポインタへのキャストから PRINTER_INFO_5 構造体へのポインタへのキャストに替えれば今度は Win95 専用になります。なお,このコードは Delphi の VCL,Printers ユニットの TPrinter.GetPrinters でも用いられている方法です。
ただし PRINTER_INFO_4 情報を要求しているため,ポートを取得できていません。これは FDelphi / Delphi Users' Forum の「サンプル: "NTでのPrinter.Printersのバグ修正"」 でも指摘されています。しかしその代わりに PRINTER_INFO_5 情報を取得するという方法では,少なくとも私の作業環境では何も取得できませんでした。というか唯一取得できるはずの情報がネットワーク上のプリンタなんだけど,これって Level が 5 だと取得できないとか?
プリンタ名の他にポートも必要ならば,Level を 2 にするべきでしょう。「いちいちドライバにお伺いを立てるらしいからパフォーマンスが気になる」という説もありますが,ぜんぜんそんな事なさそうっすよ。
正確には 5 月の半ばからでした。最近,昼間からヤケクソに眠いのですよ。正直に言って仕事がちょっと退屈だったからだとも思うけど,これは恒例の「春眠暁モード」突入を表すのです。普通は 4〜5 月あたりにこれが発症するのですが,なんだか今年はずいぶん遅かったなあ。。。とりあえずカフェインを大量に摂取できるキャンディを買いました。これを腕から注射すれば効くのかな? hehehe...
実は脳に致命的なダメージを受けていたとしたらどうしよう? いやホント,異常な眠気はシャレにならないらしいんで。。。
C++:language&libraries (cppll)
C マガなどで執筆活動もされているεπιστημηさん主催のメーリングリストです。さすが,パワーのある人ばかりが集まっているだけあり,レベルの高さの凄さは異常。comp.lang よりもはるかに強力です。
私? すいません。ROM の真っ最中です。
眠気度;32767