• 差分
  • リロード
  • 一覧
  • 最終更新のRSS

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もキーによる自動ソートを行っているらしいが、一度格納されたキーを変更したらもうアウツだと考えられる。。

Last-modified: 2011-07-03 (日) 11:28:43 (4674d)