Как работает описанный код
Библиотека 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, ...);
...
}
Код концепции
Протестировано на MacOS X 10.5.8 (Leopard) с GCC 4.0.1:
#include <stdio.h>
#include <stdlib.h>
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 <stdio.h>
buffer2: ##
Osiris JL:
Итак, эмпирическое доказательство того, что в некоторых системах описанный мною сценарий может иметь место.
Как исправить код
Исправление к коду хорошо обсуждается в других ответах. Если вы избежите проблемы, которую я иллюстрировал (например, избегая глобальных файловых указателей), это будет проще всего. Предполагая, что это невозможно, может быть достаточно скомпилировать с соответствующими флагами (во многих Unix-подобных системах флаг компилятора '-D_REENTRANT
' выполняет свою работу), и вы в конечном итоге будете использовать поточно-ориентированные версии основных стандартные функции ввода / вывода. В противном случае вам может понадобиться установить явные многопоточные политики управления для доступа к указателям на файлы; мьютекс или что-то подобное (и измените код, чтобы потоки использовали мьютекс перед использованием соответствующего указателя файла).