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

2007/06/25(Mon)

[C言語] TR24731-2 Bounds Checking via dynamic allocation

ちょっとだけ 更新、fmemopen(3)を追加してみた。
ちゃんとテストケース書かねば。

2007/06/26(Tue)

[C言語] TR24731-2

fmemopen(3)の次はopen_memstream(3)でも書こうかね。
こいつらは元々glibc拡張なんでFedoraCore6で挙動を確認してみる。

なんだかバグっぽい挙動その1:

01 #define _GNU_SOURCE
02 #include <stdio.h>
03 #include <stdlib.h>
04 int
05 main(void)
06 {
07 	char *s;
08 	size_t n;
09 	FILE *fp;
10 
11 	fp = open_memstream(&s, &n);
12 	setbuf(fp, NULL);
13 	fputc('A', fp);
14 	fputc('B', fp);
15 	fclose(fp);
16 	printf("%.*s\n", n, s);
17 	free(s);
18 
19 	return 0;
20 }

11行目setbuf(3)でバッファリングを無効にすると
12行目fputc(3)による書き込みでSEGVる。

同その2:

01 #define _GNU_SOURCE
02 #include <stdio.h>
03 #include <stdlib.h>
04 int
05 main(void)
06 {
07 	char *s;
08 	size_t n;
09 	FILE *fp;
10 
11 	fp = open_memstream(&s, &n);
12 	fputc('A', fp);
13 	fputc('B', fp);
14 	fflush(fp);
15 	fseek(fp, -1, SEEK_CUR);
16 	fflush(fp);
17 	fputc('C', fp);
18 	fclose(fp);
19 	printf("%.*s\n", n, s);
20 	free(s);
21 
22 	return 0;
23 }

15行目fseek(3)でいっこ巻き戻してるはずなので、19行目printf(3)では
"AC"が出力されるべき。にもかかわらず"ABC"と出力される。

これは仕様として正しいのかねぇ、自分にはバグとしか思えんのだが。

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を参照してください。