任何人都可以解释 ftell() 在内存流上使用时的“正确”语义。
给定以下程序:
#include <stdio.h>
#include <stdlib.h>
#include <gnu/libc-version.h>
int main(void)
{
puts (gnu_get_libc_version ());
size_t n_buffer = 1024;
char *buffer = calloc(n_buffer, sizeof(char));
FILE *file = fmemopen(buffer, n_buffer, "w");
/* "ABCD" */
static const char magic_number[] =
{
0x41, 0x42, 0x43, 0x44
};
const size_t written = fwrite(magic_number, 1, 4, file);
fprintf(stderr,"written=%d\n",written);
int fstatus = fflush(file);
fprintf(stderr,"fstatus=%d\n",fstatus);
long ftellpos = ftell(file);
fprintf(stderr,"ftellpos=%ld\n",ftellpos);
fstatus = fseek(file, 0, SEEK_END);
fprintf(stderr,"fstatus=%d\n",fstatus);
ftellpos = ftell(file);
fprintf(stderr,"ftellpos2=%ld\n",ftellpos);
return 0;
}
RHEL7 上的输出是:
2.17
written=4
fstatus=0
ftellpos=4
fstatus=0
ftellpos2=4
而 OpenSUSE Leap 42 上的输出是:
2.22
written=4
fstatus=0
ftellpos=0
fstatus=0
ftellpos2=4
(这导致我正在查看的代码中的单元测试失败)
我的问题是:
- fseek() 是否需要(按标准)使 ftell() 的结果有效?
- 这是 glibc 的错误还是行为变化?
- 为什么它不能在 OpenSUSE 上运行?
最明显的实现是将文件位置指示器作为内存缓冲区中的一个索引,分配给 fmemopen。很难看出这怎么会出错。
确实执行:
https://github.com/bminor/glibc/blob/73dfd088936b9237599e4ab737c7ae2ea7d710e1/libio/fmemopen.c
有 c->pos = pos + s; 在第 85 行。
并且大概 ftell() 只是返回 c->pos (以迂回的方式)
在 2.17 和 2.22 之间对 glibc 源代码进行了一些重组,如果我能解开它,这可能会解释这一点。但它是错误还是功能?
我不确定 Posix 和 C 标准是否完全指定 ftell 是否应该为内存流正常工作。直觉上很难理解为什么它不应该被强制执行,因为它应该只是工作。
http://man7.org/linux/man-pages/man3/fmemopen.3.html
说:
“当前位置由 I/O 操作隐式更新。它可以使用 fseek(3) 显式更新,并使用 ftell(3) 确定。”
其他手册页提到 ftell 可能不必为不是真正文件的东西工作。但是,我相信他们确实在那里考虑了设备。