○ 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)を使って楽してー、ってのが人情ですが、これには制限があって
- fp が stdinの場合
- fp を funopen/fopencookie 経由で取得した場合
には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を参照してください。