clang: ccan/tal (2)
2025/05/01
前回の続き。
talサンプルの中身
ccan/tal に載っていたサンプルをそのまま動かせるようにしただけである。
ビルドした実行ファイルの第1引数にコマンド名を書くと、カレントディレクトリにある hello
を第1引数にして実行するだけのプログラムである。
あまり動的メモリをどうこうすることがなさそうな挙動だが、まあそこはサンプルなので。
まず、構造体 1つ分を tal(ctx, struct command)
で確保する。
受け取る方は普通に struct command *
でよい。
struct command {
FILE *f;
char *command;
};
コマンドの文字列を command.command
にコピーするため tal_arrz()
で char
の配列っぽくメモリを確保する。
コマンド間を区切るスペースが一つと \0
がいるので + 2
されている。
第1引数は「このポインタに紐付いてますよ」ということだろうが、一体どこにその情報を保持しているのだろうね。
cmd->command = tal_arrz(cmd, char, strlen(a0) + strlen(a1) + 2);
その後は strcat(cmd->command, ...)
で連結している。
ということは確保したメモリはゼロクリアされているのか。
最後に tal_add_destructor()
でデストラクタらしきものを指定して終わり。
popen()
でシェルを開いて流し込む、でよいのかな。
tal
で確保したアドレスは main()
に返す。
ん、fprintf()
の第2引数はどういう意味があるんだ?
echo "This is a test\n" | cmd hello
みたいな感じか?
第2引数がファイル名になっていてパイプで文字列を受け取るコマンドが思いつかない。
まあ、そこは本題ではないからよしとしよう。
その後、最初に確保した struct command *
だけ tal_free()
して終わっている。
このタイミングでデストラクタが呼ばれるのだ。
popen()
した中身は pclose()
で実行された?
いや、popen()
でプロセスをオープンして実行するので単に出力のタイミングがそうなっただけか。
pclose()
で通常はプロセスが終わるまで待つのだから tal_free()
で同期が取れることになる。
valgrind
しかし、valgrind
すると 6ブロックくらい残っているように出てきた。
なんでだ?
あ、最初の方に SIGPIPE
が起きたと出ていた。
そういうものなんだ。
$ valgrind --leak-check=full ./app cat
==2628== Memcheck, a memory error detector
==2628== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2628== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2628== Command: ./app cat
==2628==
Hello, World!
Japanese restaurant serves sushi.
==2628==
==2628== Process terminating with default action of signal 13 (SIGPIPE)
==2628== at 0x4978887: write (write.c:26)
==2628== by 0x48EEEEC: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1180)
==2628== by 0x48F09E0: new_do_write (fileops.c:448)
==2628== by 0x48F09E0: _IO_new_do_write (fileops.c:425)
==2628== by 0x48F09E0: _IO_do_write@@GLIBC_2.2.5 (fileops.c:422)
==2628== by 0x48EFFD7: _IO_file_close_it@@GLIBC_2.2.5 (fileops.c:135)
==2628== by 0x48E2D8E: fclose@@GLIBC_2.2.5 (iofclose.c:53)
==2628== by 0x109467: close_cmd (tal_sample.c:14)
==2628== by 0x109C0F: notify (tal.c:240)
==2628== by 0x10A0EF: del_tree (tal.c:400)
==2628== by 0x10A542: tal_free (tal.c:511)
==2628== by 0x10968F: main (tal_sample.c:59)
==2628==
==2628== HEAP SUMMARY:
==2628== in use at exit: 4,544 bytes in 6 blocks
==2628== total heap usage: 7 allocs, 1 frees, 4,800 bytes allocated
==2628==
==2628== LEAK SUMMARY:
==2628== definitely lost: 0 bytes in 0 blocks
==2628== indirectly lost: 0 bytes in 0 blocks
==2628== possibly lost: 0 bytes in 0 blocks
==2628== still reachable: 4,544 bytes in 6 blocks
==2628== suppressed: 0 bytes in 0 blocks
==2628== Reachable blocks (those to which a pointer was found) are not shown.
==2628== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==2628==
==2628== For lists of detected and suppressed errors, rerun with: -s
==2628== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
雑に signal(SIGPIPE, SIG_IGN);
を追加すると全部解放されていた。
よかったよかった。
数回はメモリリーク無しになったこともあるのでタイミングによるのか?
しかしこのプログラムで 7つも動的に確保するんだな。
tal
関係でメモリを確保しそうなのが 2箇所だったのでちょっとびっくりだ。
7回の allocation
ccan/tal には 1オブジェクトあたり約 4ポインタ分と書いてある。
tal()
だけだと 1回で 48 byte alloctal_arrz()
までだと 3回で 160 byte allocpopen()
までだと 5回で 672 byte alloctal_add_destructor()
までだと 7回で 704 byte alloc
tal()
で確保するのは struct command
なのでポインタ 2つ分。
8 byteアラインメントでよいのか? 16 byteくらい。
しかし ccan/tal なので +4 ポインタ分で 6つ分。つまり 48 byte。
おお、あってる。
tal_arrz()
で確保するのは “./app cat\0” で 10 byte。
が、そんなにちょっとしか確保しないことはないのだろう。
popen()
は知らない。
tal_add_destructor()
は増分が 704 - 672 = 32
。
デストラクタ関数を紐付けるだけだが、これは元々の tal
で確保する分にはないので追加になったということか。
雰囲気的にはこの辺のコードで増えているんだろう。
もう 1つ alloc していると思うが、もうそこまで追う気力がなかった。。。
思ったより alloc されはしたが、alloc 回数を気にするような小さい環境だとそもそも使わないだろうし、 自分で解放漏れを気にしたしくみを作るのも大変だろうから、複雑な alloc が必要な場合に使うとよいだろう。