我有以下代码:

char *filedata;
FILE *f;
filedata = malloc(3);
if (filedata == NULL)
{
    fprintf(stderr, "out of memory\n");
    exit(1);
}
memcpy(filedata, "foo", 3);
f = fmemopen(filedata, 3, "r");
if (f == NULL)
{
    fprintf(stderr, "out of memory\n");
    exit(1);
}
fclose(f);
free(filedata);

现在,当我用Valgrind执行这个时,我得到以下错误:
==32454== Invalid read of size 1
==32454==    at 0x4006D33: __GI_strlen (mc_replace_strmem.c:284)
==32454==    by 0x855B7E: fmemopen (fmemopen.c:246)
==32454==    by 0x80485BF: main (in /home/tilli/memopen/a.out)
==32454==  Address 0x402502b is 0 bytes after a block of size 3 alloc'd
==32454==    at 0x4005BDC: malloc (vg_replace_malloc.c:195)
==32454==    by 0x8048548: main (in /home/tilli/memopen/a.out)

似乎fmemopen正在为我传递给它的参数执行strlen()操作。但是,手册页指出fmemopen的缓冲区参数可以是字符串或内存缓冲区(因此不必以'\0'结尾)。此外,它还指出参数的长度必须至少size个字节,事实就是这样。
这里怎么了?我刚刚在库函数中发现了一个bug吗?在极端情况下,如果fmemopen没有找到fmemopen终止符,而是继续读取未映射的内存,那么这个bug会使使用strlen的程序崩溃,这是正确的吗?
我正在运行Fedora release 12。

最佳答案

从手册来看,除非模式包含\0,否则b将用作流缓冲区的EOF标记。这解释了为什么要使用strlen()来定位EOF。
来自man fmemopen:
参数模式与fopen(3)相同。如果模式指定追加模式,则初始文件位置
设置为缓冲区中第一个空字节('\0')的位置;否则设置初始文件位置
缓冲区的起点。自glibc 2.9以来,字母“b”可以指定为模式中的第二个字符。
这提供了“二进制”模式:写入不隐式添加终止的空字节,fseek(3)SEEK_END是rela–
到缓冲区末尾的active(即,由size参数指定的值),而不是当前字符串
长度。
然而,在同一个人身上,我们可以读到:
在为读取而打开的流中,缓冲区中的空字节('\0')不会导致读取操作返回结束-
文件指示。当文件指针增大时,从缓冲区读取的数据只会指示文件结束
超过缓冲区开头的字节数。
所以你可能发现了一个虫子。

10-05 19:48