C言語、C++言語についてのちょっとした知識。
Xlib: X通信とその他のイベントの多重化I/O †
- Xlibを使ってプログラムを書く際には、以下のようなイベントループをどこかに置くのが一般的。
Display *display; // displayの初期化 // XSelectInputなどによる取得イベントの登録 XEvent ev; while(1) { XNextEvent(display, &ev); // evの値に応じた処理 }
- しかし、XNextEventはタイムアウトもないブロッキングI/Oである。 したがって、Xイベントと非同期でやってくる何らかのイベント(例えば、ネットワークI/Oなど)を並行して処理したい場合、 上のやり方では何らかのXイベントが来るまで非Xイベントも処理できず、はなはだ困ることになる。
- 解1: Xイベントループと非Xイベントループを別々のスレッドに担当させる。
- 例えばmain関数内部に上のXイベントループを書き、Xイベントループに入る前に非Xイベントループを行う別のスレッドを立てる。
- ただしこの場合、非XイベントループのスレッドからXlibの関数呼び出しをすると、高い確率でプログラムが落ちる。
- Xlibはデフォルトでスレッドセーフではないから。
- マルチスレッド対応するためのXInitThreads関数なんかも用意されているが、どこまで信用できるのか…
- 個人的には、避ける手段があるのなら、Xlibをマルチスレッドで使うべきではないと考えている。
- 解2: selectやpollを使ってイベント処理を多重化する。
- おそらくこれがベストな解。シングルスレッドで処理できる。
- XlibがXサーバとやりとりするためのファイルディスクリプタは、ConnectionNumberマクロで取り出すことができる。これを使って以下のようにする。
Display *display; int net_fd; // ネットワークI/O用のソケットFD // displayの初期化 // net_fdの初期化 int x_fd = ConnectionNumber(display); fd_set fdset; while(1) { FD_ZERO(&fdset); FD_SET(net_fd, &fdset); FD_SET(x_fd, &fdset); XSync(display, False); int ret = select((net_fd > x_fd ? net_fd : x_fd) + 1, &fdset, NULL, NULL, NULL); if(ret < 0) { perror("select error"); continue; } if(FD_ISSET(x_fd, &fdset)) { // Xイベントの処理 XEvent ev; while(XPending(display)) { XNextEvent(display, &ev); // イベント処理 } } if(FD_ISSET(net_fd, &fdset)) { // ネットワークI/Oの処理 } }
- selectを呼ぶ前にXSyncを呼ぶことがミソ。これをしないと、Xサーバにリクエストが飛ばなかったりするらしい。
- ちなみにGTKで似たようなことをする場合、gdk_input_add関数あたりでファイルディスクリプタとコールバック関数を登録するのが良さそう。
- 参考
入力ストリームのクリア †
Cランタイムライブラリでは、出力ファイルストリームの
バッファにたまっているデータを書き出すにはfflush
関数が使われる。
しかし、入力ファイルストリームのバッファにたまっているデータを
クリアする簡単な関数は見当たらない。
いくつかのWebページでは入力ストリームにfflush
を使ったり
rewind
を使ったりすることでクリアできるみたいなことが
書かれていたが、自分の環境(Ubuntu Linux 7.10, gcc 4.1.2, glibc 2.6.1)では
できなかった。
ということで、入力ストリームバッファをクリアするために次のような関数を作ってみたらうまくいった。
#define TRASH_SIZE 4096 void emptyFileBuffer(FILE *input) { static unsigned char track[TRASH_SIZE]; int input_ds = fileno(input); int old_flag = fcntl(input_ds, F_GETFL); fcntl(input_ds, F_SETFL, old_flag | O_NONBLOCK); while(fread(trach, TRASH_SIZE, 1, input) != 0) ; fcntl(input_ds, F_SETFL, old_flag); }
fcntl
システムコールで一旦ファイルアクセスをノンブロッキングモードにしてから
バッファが空になるまで読むということをしている。
システムコールを使っているので可搬性がちょっと下がってしまったか。
Win32API:ファイル出入力コモンダイアログとカレントディレクトリ †
GetSaveFileName
, GetOpenFileName
は「名前をつけて保存」「ファイルを開く」のコモンダイアログを呼び出す関数だが、この関数を何も考えないで使うと参照したファイルのディレクトリにカレントディレクトリが移動してしまうらしい。
汎用的なエディタツールなどの場合は便利な仕様だと思うが、ゲームプログラムなんかでは邪魔になる場合が多い。カレントディレクトリを変更させないようにするには、これらの関数に渡すOPENFILENAME
構造体のFlagsメンバにOFN_NOCHANGEDIR
を含ませればよい。
…ただ、MSDNを見る限りこのフラグはOSによって無効になったりならなかったりするよーな感じだ。よく分からんが。むう。
イテレータの有効性 †
STLのコンテナ中の要素を参照する場合、イテレータを使うことが多いが、コンテナから取得したイテレータを長いこと保持するときなんかはその有効性が心配になる。つまり、「イテレータを取得した後にコンテナに対して要素の追加・削除処理を行った場合、そのイテレータは正しく使えるのか?」ということが問題になる時がある。
vector
コンテナの場合、記憶領域の再確保が行われると既存のイテレータは全て無効化するらしい。一方、list
では、要素の追加なら全てのイテレータが有効のままで使えるのだとか。要素の削除の場合、削除した要素を指すイテレータは当然無効化する。
また、連想コンテナ(map,set
)の場合、insert
はイテレータの有効性を保持し、 erase
は削除された要素を指すイテレータのみ無効化するらしい。
派生クラスのコンストラクタとデストラクタ †
インスタンスが生成される時、コンストラクタは基本クラスのコンストラクタから派生クラスのコンストラクタへと順に呼ばれる。
インスタンスが消滅する時、デストラクタは派生クラスのデストラクタから基本クラスのデストラクタへと順に呼ばれる。
なお、基本クラスのデストラクタはvirtual
にしておかないとスゴイことが起きる。
class Kihon { protected: int a; public: Kihon(int a){this->a = a;}; }; class Hasei : public Kihon { protected: char *buffer; public: Hasei(int a) : Kihon(a){buffer = new char[a];}; ~Hasei(){delete [] buffer;}; };
と、こういうクラスを作ったとして、以下のコードを実行してみる。
Kihon *ptr = new Hasei(10); delete ptr;
この場合、buffer
は解放されない。デストラクタが動的結合されていないのでKihon
のデストラクタが呼ばれているのである。デストラクタにprintf
とかの出力関数を入れるとよく分かる。
これを解決するには次のようにKihon
のデストラクタを書いてやればいい。
virtual ~Kihon(){};
…他にも方法があるかな?一応私はこうやってます。
この仕様のせいでDirectDrawSurface
の解放がうまくいかず、メモリがリークしまくったことがあります。恐ろしいです。
VC++2005:Cランタイムライブラリのセキュリティ強化 †
VC++2005ではCランタイムライブラリ(CRT)の関数のセキュリティ強化が行われていて、バッファに書き込むような関数に対しては大抵バッファオーバーランを防ぐための機能を持った次世代関数が追加されている。
例えば、VC++2005のデフォルトの環境で
char buffer[10]; strcpy(buffer, "test");
とか書くと警告が出る。セキュリティが強化されたstrcpy_s
関数を使わなければいけない。
char buffer[10]; strcpy_s(buffer ,10 ,"test");
古い関数が使えなくなったわけではなくて、推奨されていないだけ。
古い関数を使いつつ警告を出さないためにはいくつか方法があるらしい。詳細は関連リンクを。
VC++2005:scanfの罠 †
scanf
のセキュリティバージョン「scanf_s
」の仕様は少し複雑である。
%d
や%lf
とかで数値データを読み込む時は従来のscanf
と(多分)同じように使うことができるが、%s
などで文字列データを読み込む場合、読込先バッファのサイズを引数として与えてやらなければならない。
つまり、このように使う。
char str[16]; int value; scanf_s("%s%d" , str ,16 ,&value);
str
の後にバッファサイズを引数として渡すのがミソ。これを忘れるとスゴイことになる。
ポインタのnewによる動的確保 †
こんなふうに書く。
int **ptr = new int *[15];
なにげに、new (int *)[15]はエラーになる。(VC++5.0 , g++で確認。)
STL:比較関数オブジェクトless(VC++5.0) †
#include <queue> ... std::priority_queue<int>
て感じで使おうとすると、「less
は定義されていません」というエラーがインクルードファイル内で起こる。 less
というのはpriority_queue
のデフォルトの比較関数オブジェクト。従って比較関数オブジェクトを自前で与えた場合はこのエラーは出ない。 priority_queue
を使う時にはless
が定義されているfunctional
というヘッダもインクルードしなければいけない。
一方、同じく比較関数オブジェクトを必要とするmap
の場合、#include <map>
とやっておけばそのままless
が使える。何なんだこの仕様は。
…ま、最近のバージョンでは直っているような気がするけど。
STL:priority_queueの値更新 †
priority_queue
に既に格納されたオブジェクトの優先順位を外部から変更しても、pop
で取り出される順番は変わらない。
つまり、priority_queue
にオブジェクトを格納したら、その優先度を変えてはいけない。
map
もキーによる自動ソートを行っているらしいが、一度格納されたキーを変更したらもうアウツだと考えられる。。