【发布时间】:2021-01-13 15:06:11
【问题描述】:
我正在编写一个小型 C 库,用于读取作为 FILE * 传递的 gzip 文件。我正在使用zlib的gzdopen()打开文件文件描述符:
int zOpenCloseTest(FILE *const plainFile) {
gzFile file = gzdopen(fileno(plainFile), "rb");
if(file == NULL) {
goto error;
}
if(gzclose_r(file) != Z_OK) {
goto error;
}
return 0;
error:
// gzdopen does not close fd if it fails
fclose(plainFile);
return -1;
}
int main() {
FILE *const file = fopen("test.xp", "rb");
if(file == NULL) {
return -1;
}
if(zOpenCloseTest(file) < 0) {
return -2;
}
return 0;
}
zlib manual 声明:
文件描述符是从
open、dup、creat、pipe或fileno等调用中获得的(在该文件之前已使用fopen打开)。 [...] 对返回的gzFile的下一次调用gzclose也将关闭文件描述符 fd,就像fclose(fdopen(fd), mode)关闭文件描述符fd一样。如果您想保持fd处于打开状态,请使用fd = dup(fd_keep); gz = gzdopen(fd, mode);。 [...] 如果您使用fileno()从FILE *获取文件描述符,那么您将不得不使用dup()以避免双重close()ing 文件描述符。gzclose()和fclose()都会关闭关联的文件描述符,所以它们需要有不同的文件描述符。
我不想让文件保持打开状态,所以我不创建重复项。
当使用 Valgrind memcheck (--leak-check=full --show-leak-kinds=all --track-origins=yes --vgdb=no --track-fds=yes) 检查时,上述代码会在第 21 行 (fopen) 上创建 Leak_StillReachable 错误。
在 Windows 上,它会因“调试断言失败!”而崩溃。 (_osfile(fh) & FOPEN) close.cpp line 49 错误框。
两者都是在“调试模式”下构建的。
该库是更大的 CMake 项目的一部分,用户可以选择将其构建为静态库还是共享库。无论哪种情况,我都希望 zlib 静态链接到库。
我最初认为崩溃是由于库和 zlib 之间的运行时库链接模式不匹配,但最小的示例表明情况并非如此?
如果您检查 CMake 输出,您可以看到 zlib 正在使用 /MDd 标志构建,应该是这样。
(现代)CMake 通常默认为 CMAKE_MSVC_RUNTIME_LIBRARY 的“MultiThreaded$DLL”。
我相信我忘记了关于文件描述符流关系如何工作的重要内容。 code is available on GitHub。该项目使用 Hunter 以 CMake 友好的方式自动设置 zlib。 来源是跨平台的,mre 提供了安静 msvc 警告的定义。
【问题讨论】:
-
我猜潜在的问题是
gzclose关闭了文件描述符,但对它上面的 stdio 流一无所知。这类似于在 stdio 流上执行close(fileno(fp));。你也许可以这样做:fd = dup(fileno(plainFile));fclose(plainFile);file = gzdopen(fd);if (gzclose_r(file) != Z_OK) {close(fd);return -1;}。 MS CRT 可能希望您改用_dup和_close! -
问题是 zlib man 明确声明“在返回的
gzFile上的下一次调用gzclose将也关闭文件描述符fd,就像@ 987654365@。”fclose接受FILE*并关闭整个事物,而不仅仅是描述符。我还强调了看起来它建议不要在gzclose之后调用fclose的部分。关于 mscrt 警告,我将添加一条注释。 -
谢谢!你和 Mark Adler 让我意识到我真的需要修改我对 io 的了解。在结束这个问题之前,我借此机会问一下,你为什么在
gzclose_r的错误处理中加入了close(fd)?gzclose失败可以让描述符保持打开状态吗?这样您就可以一步处理这两个错误,因为如果gzdopen失败,那么保证gzclose也会失败? -
看起来
gzclose总是在文件描述符上调用close,除非它返回Z_STREAM_ERROR。如果返回值为Z_ERRNO则close返回错误,
标签: c valgrind zlib c99 msvcrt