Not only is the Internet dead, it's starting to smell really bad.:2007年06月29日分

2007/06/29(Fri)

fmemopen/open_memstream

バールStringIOみたいなものなんでしょうか?

Yes, PerlのIO::String、Python、RubyのStringIOと同じですかね、LLよう知らんけど。
Javaなんかだと

FILE				→ interface java.io.OutputStream
fopen/fdopen			→ class java.io.FileOutputStream
fmemopen/open_memstream		→ class java.io.ByteArrayOutputStream

という関係になるかと。

元々*BSD系ではFILE + fread/fwrite他はfile descriptor操作の為だけのものではありません。
funopen(3)を使うことで、どんなstreamでもfread/fwrite経由で読み書きできる汎用インタフェースになってます。

#include <sys/cdefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

int
readfn(void *cookie, char *s, int n)
{
	gzFile *p = (gzFile *)cookie;
	return gzread(p, (voidp)s, n);
}

int
writefn(void *cookie, const char *s, int n)
{
	gzFile *p = (gzFile *)cookie;
	return gzread(p, (voidp)__UNCONST(s), n);
}

fpos_t
seekfn(void *cookie, fpos_t offset, int whence)
{
	gzFile *p = (gzFile *)cookie;
	return (fpos_t)gzseek(p, (z_off_t)offset, whence);
}

int
closefn(void *cookie)
{
	gzFile *p = (gzFile *)cookie;
	return gzclose(p);
}

int
main(int argc, char **argv)
{
	const char *s;
	size_t n;
	FILE *fp;
	int ch;

	if (argc != 2) {
		fprintf(stderr, "usage: %s [filenname[.gz]]\n", getprogname());
		return 1;
	}
	s = argv[1];
	n = strlen(s);
	if (n > 3 && s[n - 1] == 'z' && s[n - 2] == 'g' && s[n - 3] == '.') {
		gzFile *cookie;
		cookie = gzopen(s, "r+");
		fp = funopen(cookie, &readfn, &writefn, &seekfn, &closefn);
	} else {
		fp = fopen(s, "r+");
	}
	while ((ch = fgetc(fp)) != EOF)
		printf("%c\n", ch);
	return 0;
}

↑のような使い方をすれば、zlibの存在を隠蔽できます。

glibcにもfunopen(3)とほぼ同等のfopencookie(3)がありますが、
fmemopen/open_memstreamといった文字型配列操作に特化した関数は
こんな↓使い方を想定して生まれたんじゃないかなと。

int
vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
{
	struct FILE *fp;
	size_t n;

	fp = fmemopen((void *)s, n, "w");
	if (fp == NULL)
		return -1;
	return vfprintf(fp, fmt, ap);	
}

int
vasprintf(char **p, const char *fmt, va_list ap)
{
	struct FILE *fp;
	size_t n;

	fp = open_memstream(p, &n);
	if (fp == NULL)
		return -1;
	return vfprintf(fp, fmt, ap);
}

ただglibcのvsnprintf/vasprintfの実装はこうはなってませんが。

アプリでの使い方としては - 例えば次のようなインタフェースを考えた場合

/*
 * 文字列を解析して設定情報を読み取る
 */
int
parse_config_string(const char *s, size_t n)
{
	int ch;

	for (;;) {
		ch = (int)*s;
		++s, --n;
		...

		--s, ++n;
	}
}

/*
 * ファイルを解析して設定情報を読み取る
 */
int
parse_config_file(FILE *fp)
{
	int fd;
	struct stat st;
	char *s;
	size_t n;

	fd = fileno(fp);
	fstat(fd, &st);
	n = (size_t)st.size;
	s = mmap(NULL, n, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, (fpos_t)0);
	return parse_config_string(s, n);
}

上記のようにmmap(2)を使って楽してー、ってのが人情ですが、これには制限があって

にはmmap(2)に失敗しますので正常に動作しなかったりします。

その制限は困るとゆー場合

int
parse_config_file(FILE *fp)
{
	int ch;
	for (;;) {
		ch = fgetc(fp);
		...

		ungetc(ch, fp);
	}
}

int
parse_config_string(const char *s, size_t n)
{
	FILE *fp;
	fp = fmemopen(s, n);
	return parse_config_file(fp);
}

としてしまえばいいでしょう、まあ性能は落ちるでしょーが。

こまかい仕様についてはSC22/WG14の TR24731-2を参照してください。