write関数の変な使い方

ゴルフの話ではないです(ゴルフに役立たないとは言っていません)。

C言語write関数は、unistd.hで宣言された、ファイルディスクリプタを通じて書き込む関数です。その宣言は以下のようになっています。

ssize_t write(int fd, void* buf, size_t len)

コードゴルフであればfdに変なファイルディスクリプタを渡したりするところですが*1、今回はそういう話ではないです。

次に変なものを渡せるのは、bufです。ここに変なポインタを渡すと、len0でなければエラーになり、write関数の返り値は-1になります。 errnoは、14、つまりEFAULTにセットされます。

len0でなければ」なので、len0なら正当です。write関数の返り値は0になります。

一見意味のない行為に思えますが、どうもfflushの実装に使えるようです。実際、軽量libc実装のmuslでは、このテクニックが使われています。

git.musl-libc.org

(該当箇所引用)

 /* If writing, flush output */
    if (f->wpos != f->wbase) {
        f->write(f, 0, 0);
        if (!f->wpos) {
            FUNLOCK(f);
            return EOF;
        }
    }

エミュレータでこのコードを動かした時、おかしな挙動になっていて苦労したという話でした。

システムコールエミュレータが「ポインタbufが未割当の領域を指していておかしい!」と早合点する実装になっていたのです。

*1:read関数の第一引数に0(stdin)ではなく1(stdout)を渡して読み込まないというのは常套手段ですが、write関数でこういうことをやるのはあまり見たことがない気がします。不勉強なだけかもしれません。