Skip to main content

ログ取得ツール

Category: プログラミング

そいつを解決しよう

シンボルを解決(というか逆かな?)してみようと思い立って、libbfdを使ってsymlib(mydns.jp)を作ってみた。ちょっとした遊び。 使い方は、 #include : struct symtable *st; char *sym; int offset; st=symbol_load(“some-executable”, 0); if(st==NULL) exit(1); sym=symbol_get(st, some-symbol, &offset); if(sym==NULL) exit(1); printf("%p = %s+%d\n", some-symbol, sym, offset); symbol_close(st); みたいな感じ。現実的には、main()argv[0]を読み込ませればいいんじゃないかな。 しかしシンボルの解決というのはまあまあ重要な問題かと思う。わけのわからんポインタを与えられたときに、しかしヒープやスタックではなく関数ポインタだったりするのだが、そこが果たして何者であるのか、というのはデバッグに必要な情報だ。printfの%pをオーバーライドしたいくらいだがそこまではなし。 今は指定したファイルのシンボルしか読み込まないのだが、これから拡張するとしたらリンクされているシェアードライブラリのシンボルまで読み込むようにしたり、自分のexecutableを勝手に読み込むとか、ksymoopsみたくするとか、いろいろ考えられる。やはりprintfの%pを奪って環境変数LD_PRELOADの設定だけで使えるようにするというのが本命かもしれない。しかしprintf奪うなんてのはかなり気乗りのしない出来事。 冗談でPythonからも使えるようにしたが全く意味ないね(笑) (追記) 2003-11-17 23:53 用途をあんまり書いてなかったな。関数ポインタを引数に取る関数で、与えられた関数ポインタが何という名前の関数であるかをログに残すときに使える。デバッガで見りゃいいんだけど、そうもいかないときにどうぞ。 (追記) 2003-11-17 25:00 こういう、ある種の「逆eval」ってのはおもしろいかも。ちょっとPrologっぽい? …そこまで言うと飛躍しすぎだな。 (追記) 2003-11-18 15:56 あと、ヒープのアドレスから「どの関数でいつ割り当てられたメモリか」を求めるとか、スタックのアドレスを渡されてスタックフレームを表示するとか、バックトレースを表示するとか、そういう機能ってあるといいと思う。 私はバックトレースに関してはデバッガを使っているが、この程度のことを知るためにいちいちデバッガかと思うと納得がいかないのだ。 あとデバッガが構造体を探る様子が楽しいので、その機能もライブラリにしたいなぁと日頃思っていたことを思い出した。いわゆるprintfデバッグだと構造体のダンプってみんなよくやると思うけど、こんなの人力で書かせるなよと思う。Pythonで言うところの__repr__みたいなのをCで使いたい人がいるとして。…__repr__ってレプリケーションのことかな。 (追記) 2004-10-04 12:06 __builtin_return_addressとかのスタックフレームを操作する関数と組み合わせれば、スタックフレームをシンボルで表示できるな。けっこう使えるのかもしれない。

走るASCII Art

眠いときに見るプログラムコードって単なるASCII Artのように見える。具体的に書くと、画面上に見えている、何らかの意味があるはずのコードが、

 *** **
  ***** ******************************** ***********
  ***** ****************************** **************************************
  ******************************** ************************** *****
    ************************************************
      *** **
      *********** ************** **************
      ***************************************
      ***************************************
      ***************
      ******** ******************* *****
        *** ***********************
        *************************
          **********************************
          *********************************** ***********************
                        ***** ******************* ******************
                        ***********************
          *************************
          ********************************************************************
          ***************
        *

のように見える。…ってこれが「人間の首から上」に見えるのはオレだけか? ちゃんと字になってるともっと臨場感があるんだけどそのままこんなとこには書けないな。

なつかしい…

ここ(hatena.ne.jp)から見つけたサイト、プログラム投稿勧告(dion.ne.jp)。 ベーマガ、昔買ってたなぁ。あれは誌面にリストが全部載って、手で打ち込むというスタイルをとることによってコードの美的感覚もでてくるし、コード量も少なくなる方向にいっていた。赤の他人が、実行する前に印刷されたソースを見て、さらに手で打ち込んでくれるわけだから。 いつしか買うのをやめてしまったのだが、私がベーマガを買っていた頃ってのは、バイナリダンプを延々と載せ、平気で読者に手入力させていた時代だ。半分以上を広告に占領されてた雑誌なんてのもあったな(笑)。しばらくして5インチのフロッピーが付録につく雑誌が出てくるようになるが、ベーマガにはついてこなかった気がする。まあ、とにかくベーマガが一番ゲーム&ソース指向(?)で、ファンが多かった。それで、みんなベーマガ買ってたよねって話になると今だに盛り上がったりする。ある種、一本スジが通ってたんだろうな。 今は開発環境が充実したためかコードもデータもどんどん長くなって複雑化している。当時のBASICになかった「データ構造」というものが表現できるようになったというのがでかいと思うのだが、ソースを読みたがらない人の割合が非常に多くなった。 思うに書く側にも読む側にも問題があって、書く側は「手で打ち込むなら」という視点を持てば少しは良くなると思う。読む側は…横着するのはいいけど横着しすぎるのは良くない。読むためのツールは揃っているのだ。 私もそういう点ではあまり良いプログラムを書いている気がしないが、「別の用途で使うなら」という視点がある(と思ってる)のと、「プログラミングは一般化(下位のレベルを書く)と特殊化(上位のレベルを書く)の繰り返しだ」という信念みたいなの(?)が特徴だと自分では思っている。評判は悪いが(笑)。 …しかしプログラム投稿勧告(dion.ne.jp)、よい感じのサイトだな。

これはまた苦い思い出になるのかな

sox(sourceforge.net)のMP3エンコーダ(lameのライブラリを使う)にはロクなオプションを渡せない。sox自体のオプションに、エンコーダに特有なオプションというのが与えられないからだ。 これは困った。クオリティやビットレート、VBRの有無など設定すべき項目はたくさんあるのに。…というわけで本来ならsoxのオプションにエンコーダへのオプションという項目を加えるのが正しいと思うが、しょうがないので環境変数LAMEOPTで渡すように書き加えてみた。 しかもこの追加部分、きったない書き方になった!! 解決方法も邪道、書き方も邪道。ま、動作してるからいいか、と納得するのがイヤだがここは自分の顔を立てて使っていくしかない(笑)。 で、なんでこういうことを思ったのかというと、どういう制約かなんかしらないけど、16bit stereo 44.1kHzで録音していると、3時間半くらいで切れてしまうのだ。ちょうど2GBの地点である。そんなバカな。

# rec -r 44100 -c 2 -s w -d /dev/dsp -t wav - | lame -h - out.mp3

で、それってどうよ、と思って試みに直に出してみたら4時間以上全く問題なかった。

# rec -r 44100 -c 2 -s w -d /dev/dsp -t mp3 out.mp3 

soxやlameの問題じゃないっぽいね。どちらも_FILE_OFFSET_BITS=64でコンパイルされてるし。wavファイルのファイル形式の制約かもしれない。というわけで「-h」(-q 2と同じ)を与える必要があったのだ。あとはデフォルトでも当面は問題ない。 MP3にはこういう問題はないのかな。120kbpsで2GBっつーと…40時間くらいかな。さすがにそこまで録音する根性はない(人間側の問題で)。 (追記) 2003-11-12 18:38 あ、そうだ、soxのライブラリをPythonで使えるようにしようかなー。と思ってswigを使ってやってみた。いくつかシンボルが足りなかった。 %module sox %{ #include void cleanup(void){} %} %include 特に、gsm_createがひどくて、soxソース/gsm/libgsm.aにあるんだけどこれは普通インストールされないんだよね。しょうがないから強引にやった。

今さらだけど、PHPの文字列処理ってもしかして貧弱では?

普段Pythonを使っていると(最近はPerlもそうなのかな)、文字列というものが内部ではUnicodeになっているものだと考えてしまう。正規表現のマッチングや文字列の置換は日本語でも問題なく通る(1文字ずつUnicodeになってるから)ものという前提でプログラムを書いてしまうのだ。 PHPはそうではない。「 」(全角空白:EUCで0xA1A1)を「 」(半角空白:0x20)に置換するとしよう。PHPはCのstr系の関数と同様、バイト列の中からA1 A1という並びを検索して、それを20に置換する。ところがEUCやSJISの特性から言って、文字という区切りがないまま2バイト系と1バイト系が混在するため、たとえば「優勝 」に含まれる全角空白を半角空白に置換しようとしても、CD A5 BE A1 A1 A1→CD A5 BE 20 A1になって化け化けになってしまう。この例はEUCだがSJISでも同じである。JISのようにkanji-inとkanji-outがあるともっと悲惨なのだがそれはひとまず置いておく。プログラマが文字を前から1バイト系2バイト系と分類して数えていって、ちゃんと処理しなければならない。 Unicode嫌いな人もいるけど、EUCやJIS、SJISのコードを生のバイト列で保持し、いちいち文字区切りを考えながら処理するというのはバカバカしいと思う。そこまで苦労しても日本語にしか対応できないのかよ、というのもあるし、日本語だろうと英語だろうと、1文字は1文字だ、それを自分で数えんのかよ。文字列型があるのにバイト列と文字コードを意識しなければならない、ってのがなんかイヤだ。でもUnicodeも実は固定長じゃないらしいんだよな(弱)。 …自分でCのときにcharじゃなくてwchar_t使ってるかと言われるとそうじゃないというのもあるな(笑)。でもCには文字列型はないからバイト列でもいいだろう、と言い訳をしておく。 そうそう、PHPにはmbstringがあるけど、正規表現がドキュメントはあるけど関数が定義されてないね。やられた。コンパイル方法のせいかな? でもmbstringは有効になってるのだが…。 Pythonはたぶん、挙動からして文字列型にエンコード情報が入ってるのではないかと思う。違うコード系のものを連結するとお互いをUnicodeに変換してから連結したりすんのかな。 何言ってるのかわかんなくなってきたけど、とにかくプログラマの負担が軽減されてほしいというのは常に思っている。文字列の中身の意味の処理(文字コード以外での言語依存処理)はアプリでやるとしても、文字コードには気を使いたくない。 (追記) 2003-11-08 26:33 ついでに言えば、MySQLもPHPと同じで、テキストを単なるバイト列として扱っていますので、短い単語で検索すると、その単語が含まれていない文書までひっかかる。いちおう終端くらいは見てるかも。たぶんPythonで実装されたSQLサーバgadfly(python.jp)とかを使うとちゃんとひっかかるものはひっかかり、ひっかからないものはひっかからないのではないかな。調べてないけど。

親のスタックかじり

呼び出し元(親)のスタックをいじって変数を置けたらさぞかしC言語も書きやすくなるだろうなぁ、と思った。

s/orig/new/g;

を考えた場合、C言語では例えば以下のようになる。

{
  int r;
  char *text="test text da orig";
  char new_text[BUFSIZE];

  r=my_regexp_replace("orig", "new", text, new_text, BUFSIZE);
  if(r){
    printf("ERROR: my_regexp_replace(...)=%d\n", r);
    return -1;
  }
}

おまえはアホかと言いたい。エラー処理のためのtry-catchがないのはしょうがないとしても、せめて以下のように書かせてほしい。

{
  int r;
  char *text="test text da orig";
  char *new_text=my_regexp_replace("orig", "new", text);
  if(new_text==NULL){
    printf("ERROR: my_regexp_replace(...)=NULL\n");
    return -1;
  }
}

ここでミソなのは、new_textの実体がスタックに置かれるということ。親のスタックをかじれるCをここで「親スタC」と省略して呼ぶことにすると、親スタCではmy_regexp_replaceが親スタック内に可変長でデータを確保できるので、戻りテキストデータを親スタック内に確保してそこに置けるのである。親関数は戻り値を見て処理するが、あえて開放する必要はない。期限(スコープ)が切れれば勝手に開放される。無論GCのほうが良いことは間違いないけど、親スタCってGCよりは実装が簡単じゃない? いつも思うのは、malloc等で確保された領域を返すとメモリリークが起きるし開放の作法が問題になる(いわゆるstrdup問題※)。そこでポインタを含む引数をたくさん並べることになるのだが、これがプログラムを悪くしているように思う。数あるメモリの中で(そんなたくさんの種類はないけど(笑))、スタックはCに深々と組み込まれた共通基盤である。活用しない手はない。 …でもスタックの構造を考えるといろいろ問題があるよな。そんなこと言ったら身もフタもないが(笑)。 (※)strdup問題というのは、strdupの戻り値でポインタが返ってくるバイト列がmallocというstring系でない関数で確保された領域であるということだ。だからプログラムは「strdupで確保したメモリをfreeで開放する」のようなチグハグな作りになってしまう。ここで「strdupで確保したものをstrfreeで開放する」のように厳密にやってstring系の関数が全部こうなれば良いのだが、実際はさらに面倒なことになりそうだからか思想上の問題か知らないけどCはそこまでやっていない。

HTMLので出るフォームにも改行を入れられることを「発見」してしまった。そうかそう来たか。油断できん。 コピペで入力してたら意図していない改行入りの猛烈に長い行が送信されてしまったことにより気がついた。表示されるのは最後の行のみ。これってけっこう悪用できるかも? これをtype=“hidden"のかわりに使うこともありうる…ありうる? (追記) 2003-11-05 14:11 っていうか今日まで、タイトルの欄はhtmlspecialchars()を通してるのかと思ってた。

ビット幅1のintは何を表現できるか

Cで、ビット幅1のintを書くとしよう。

struct sample{
  int  flag:1;
};

で、この1ビットで現せる1ビット幅の整数というのは「0と1」で確定かというと実は違う。実際に確かめてみると、表現できるのは「-1と0」あるいは「0と1」の2通りがあるようだ。 #include struct sample{ int flag:1; }; int main(int argc, char **argv) { struct sample s1; s1.flag=0; printf(“0=%d\n”, s1.flag); s1.flag++; printf(“1?=%d\n”, s1.flag); if(s1.flag>0){ printf(">0\n", s1.flag); } if(s1.flag<0){ printf("<0\n", s1.flag); } return 0; }

  • gcc-3.2.3とgcc-2.96では「-1、<0」。
  • tcc-0.9.19では-runオプションをつけて直接実行すると「-1、>0」、コンパイルすると「-1、<0」。同じコンパイラなのに相反する結果になった。
  • Sun WorkShop Compilers 5.0 98/12/15 C 5.0では「1、>0」。

「-1、<0」と「-1」と評価する処理系は一見不自然だが、トップビットが立ってる上に全部のビットが立っているし、-1というのも根拠がある。-(-1)を評価しても…えーとnegateは1を引いて全部のビットを反転するんだっけ…やっぱり-1になり、恐らく1==-1が成立するだろう。 さてどちらが正しいのか。どちらも正しいような気がする。もしかしたら不定値なのかもしれない。つまりビット幅1ならunsignedにして「1、>0」を確定させるか、不定として「!=0」であるかどうかしか判定しないか、どちらかの書き方をするしかない。もともと1ビット整数に大小関係など正確に定義することなどできないのだから。 ただ、私としてはGCCに肩入れするわけではないが、「-1」説を採りたい。理由は、そのほうが整合性があるからである。例えば2ビットで表せる符号つき整数は「-2(10)、-1(11)、0(00)、1(01)」であり、プラスになる数値よりもマイナスになる数値のほうが1つ多い。この関係はどんなビット幅のintでも同じである。ビット幅1だけを特別扱いすることはない。 ちなみにunsigned flag:1;のようにするとprintfの%d変換でも「-1」ではなく「1」になる。int:1をintにキャストしたときに-1とするか1とするかはコンパイラ依存だが、unsigned:1をintにキャストするときは必ず1になる。unsignedはトップビットで符号を判別しないしプラスにしかならないからね。役には立たないが覚えておこう。 まあでも、仕様上どうなっているのかは調べてないのでそのへんは勘弁してほしいっす。 (追記) 2003-11-02 15:03 tcc-0.9.20が出てました。試したけど0.9.19と同じだった。あと記述が間違っていた。 tccで直接実行(-runをつける)は「1、>0」ではなくて「-1、>0」でした。-1で0より大きいとはこれ如何。

いつも迷うこと

C言語の多重配列について。我の場合は仕事でも多重配列を使う必要があるようなアルゴリズミックなプログラムは書いてないので(そのかわりけっこう複雑な構造体を好んで使う)、書いとかないと忘れるんだよな。 何が言いたいのかというと、a[10][20]のような多重配列をつくるときに、どちらからループを回すのが正しいか、というのが迷うのだ。普通に考えれば(a[10])[20]だから、a[10]が20個ある配列で、10から回すのが正しいと言えるだろう。でもそうだったっけ?? と思うのだ。逆じゃなかったかな。20個の配列が10個ある、ようなレイアウトにされてしまったような気がする。毎回のようにテストプログラムを書いて調べることになるのでメモしておく。 実際のメモリレイアウトは以下のようになる。

a[0][0] ... ptr
a[0][1] ... ptr+1
    :
a[1][0] ... ptr+1*20
a[1][1] ... ptr+1*20+1
    :
a[2][0] ... ptr+2*20
a[2][0] ... ptr+2*20+1

以下のように宣言にカッコをつけてもレイアウトに変更はない。

int (a[10])[20];

だから、多重配列は常に後ろの添字、20のループから回すのが正しい。 ここで興味深いのは、a&a*aを評価するとみな同じ値になるのである。なんでこうなるのか、理由がわかるかね、ふふふ。 補足すると、このような理論なので、関数の引数にする場合は先頭の添字のサイズは省略できるが後ろのほうは省略できない。引数を使わなければエラーは吐かない。また、配列のサイズを指定したところでアドレス計算だけに使うため、境界チェックなどやろうともしないという、C言語らしさを見せている。コンパイルオプションで配列の添字チェックをつけるくらいのやさしい心遣いがあってもいいと思う。願う。まあ、コンパイラにつけちゃうとpurifyとか作ってる人が困るとか、あるのかな。でもCに境界チェックがないというのは、欠陥ソフトが量産される背景にもなってるんじゃないかと思う。自分でもそういうソフト書いちゃうしね。わかっていながらあえて書くこともある。

PHPメモ

「@」をつけるとエラー出力を抑制できる。これは演算子として定義されている。 filemtime("not-exist.file")はファイルがないとエラーを吐くのだが、@filemtime("not-exist.file")はエラーを抑制できるのだ。 存在しない連想配列のインデックスにも使える。$array["not-exist"]はエラーだが、@$array["not-exist"]はエラーを吐かない。 けっこう便利だ。これで、どうでもいいif文をけっこう減らせるよね。