描述的代码如何出错
stdio库缓冲数据,分配内存以存储缓冲的数据 . GNU C库动态分配文件结构(某些库,特别是在Solaris上,使用指向静态分配的文件结构的指针,但缓冲区仍然是动态分配的,除非你设置缓冲否则) .
如果您的线程使用指向全局文件指针的指针的副本(因为您将文件指针作为参数传递给函数),那么可以想象代码将继续访问已经分配的数据结构(甚至虽然它被关闭释放了,但是会从已经存在的缓冲区中读取数据 . 只有当您退出函数或读取超出缓冲区内容时才会出现问题 - 或者先前分配给文件结构的空间将重新分配以供新用途使用 .
FILE *global_fp;
void somefunc(FILE *fp, ...)
{
...
while (fgets(buffer, sizeof(buffer), fp) != 0)
...
}
void another_function(...)
{
...
/* Pass global file pointer by value */
somefunc(global_fp, ...);
...
}
概念证明代码
使用GCC 4.0.1在MacOS X 10.5.8(Leopard)上测试:
#include
#include
FILE *global_fp;
const char etc_passwd[] = "/etc/passwd";
static void error(const char *fmt, const char *str)
{
fprintf(stderr, fmt, str);
exit(1);
}
static void abuse(FILE *fp, const char *filename)
{
char buffer1[1024];
char buffer2[1024];
if (fgets(buffer1, sizeof(buffer1), fp) == 0)
error("Failed to read buffer1 from %s\n", filename);
printf("buffer1: %s", buffer1);
/* Dangerous!!! */
fclose(global_fp);
if ((global_fp = fopen(etc_passwd, "r")) == 0)
error("Failed to open file %s\n", etc_passwd);
if (fgets(buffer2, sizeof(buffer2), fp) == 0)
error("Failed to read buffer2 from %s\n", filename);
printf("buffer2: %s", buffer2);
}
int main(int argc, char **argv)
{
if (argc != 2)
error("Usage: %s file\n", argv[0]);
if ((global_fp = fopen(argv[1], "r")) == 0)
error("Failed to open file %s\n", argv[1]);
abuse(global_fp, argv[1]);
return(0);
}
在自己的源代码上运行时,输出为:
Osiris JL: ./xx xx.c
buffer1: #include
buffer2: ##
Osiris JL:
因此,经验证明在某些系统上,我概述的情景可能会发生 .
如何修复代码
在其他答案中很好地讨论了对代码的修复 . 如果你避免我说明的问题(例如,通过避免全局文件指针),这是最简单的 . 假设这是不可能的,用适当的标志进行编译就足够了(在许多类Unix系统上,编译器标志' -D_REENTRANT '完成了这项工作),你将最终使用基本标准的线程安全版本I / O功能 . 如果做不到这一点,您可能需要围绕对文件指针的访问放置明确的线程安全管理策略;一个互斥体或类似的东西(并修改代码以确保线程在使用相应的文件指针之前使用互斥锁) .