почему fclose() не всегда сбрасывает данные на диск?

Я получаю странные результаты, пытаясь записать данные в файлы в C.<br/>. Я думал, что fclose() закрывает *FILE и flushes данные из своего буфера в file.<br/> Но по какой-то причине он только иногда сбрасывает данные в моей программе, и это не так. не делай этого в другое время.

Например: я запускаю этот код, и в моем файле я вижу две строки. (Отлично, именно то, что я хочу)
Но затем, когда я запускаю код следующие 4 раза, он ничего не меняет в моем файле. А потом, когда я запускаю ее в другой раз, внезапно появляются 10 дополнительных строк (8 с последнего запуска программы и 2 с этого момента)
(Эти 4 раза просто пример, иногда это 5, 8, 10 или даже всего 2 раза, прежде чем я увижу вывод)

Я правда этого не понимаю? Разве данные не должны быть видны после каждого запуска программы? Где вообще хранится этот буфер между разными запусками программы, потому что программа каждый раз завершается, поэтому память освобождается, Правильно?

(Кстати, я также пробовал fflush(fd) до и после fclose(), но это не решило проблему)

#include <stdio.h>


int main(int argc, char const *argv[]) {
  FILE * fd;
  fd = fopen("test_file.txt", "a");
  fprintf(fd, "String 1\n");
  fprintf(fd, "String 2\n");
  fclose(fd);
  return 0;
}

person piet_lu    schedule 02.07.2020    source источник
comment
Как посмотреть полученный файл? Какую операционную систему вы используете? Какая файловая система?   -  person Gereon    schedule 02.07.2020
comment
Возможно, вы оставляете открытым текстовый редактор, который используете для просмотра содержимого файла, и который не обновляется. Возможно, вам придется перезагрузить файл, и некоторые редакторы предупреждают вас, что содержимое файла изменилось.   -  person Weather Vane    schedule 02.07.2020
comment
Проверьте возвращаемое значение: if (fclose(fd)) perror("fclose");. Если ошибки нет, fclose() включает эквивалент fflush(): см. C11 7.21.5.1.   -  person pmg    schedule 02.07.2020
comment
В таком случае в идеале следует проверить результат всех четырех операторов ввода-вывода, чтобы убедиться, что они должны быть выполнены успешно.   -  person Weather Vane    schedule 02.07.2020
comment
То, что @WeatherVane отметил о проверке возвращаемых значений из операторов ввода-вывода, имеет решающее значение. В опубликованном коде вы не можете знать, сработали ли ЛЮБЫЕ ваши звонки. Также обратите внимание, что данные в операторах fprintf() могут быть не записаны в файл до тех пор, пока не будет вызван fclose(), и что fclose() может завершиться ошибкой после того, как все другие ваши вызовы оказались успешными, поскольку данные были буферизованы, а не фактически писалось до тех пор, пока fclose() не сбросил его. Если ваш диск переполнится или возникнут другие проблемы, все предыдущие успешные вызовы fwrite() могут быть стерты.   -  person Andrew Henle    schedule 02.07.2020
comment
Дополнение к моему комментарию о текстовых редакторах: хотя некоторые и предупреждают об изменениях в файле, я никогда не использовал текстовый редактор, который автоматически обновляет представление, вероятно, потому что это было бы плохой политикой.   -  person Weather Vane    schedule 02.07.2020
comment
Появляются 10 дополнительных строк --› Что вы используете для чтения файла?   -  person chux - Reinstate Monica    schedule 02.07.2020
comment
вам нужно проверить возвращаемое значение fclose, также вызов fclose не гарантирует, что данные уже находятся на диске. см. мой ответ ниже. благодарю вас.   -  person nor    schedule 02.07.2020
comment
Когда я беру ваш код и запускаю tail -f test_file.txt в другом терминале, я каждый раз получаю дополнительные строки. Я согласен с другими, проблема, скорее всего, в том, что вы используете для просмотра файла.   -  person Zan Lynx    schedule 02.07.2020


Ответы (1)


Благодаря майклу керриску у нас есть все ответы, которые нам нужны в linux справочные страницы. Если вы покопаетесь в справочных страницах и увидите раздел «Примечания» (перечисленный ниже в моем ответе), вы поймете это поведение.

int fclose(FILE *stream); - закрыть файловый поток, на который указывает stream.

ОПИСАНИЕ

Функция fclose() сбрасывает поток, на который указывает stream (записывая любые буферизованные выходные данные, используя fflush()) и закрывает лежащий в основе файловый дескриптор.

ПРИМЕЧАНИЯ

Обратите внимание, что fclose() сбрасывает только user-space buffers, предоставленный библиотекой C. Чтобы гарантировать, что данные физически хранятся на диске, буферы ядра также должны быть очищены, например, с помощью sync() или fsync().

Так что недостаточно обеспечить запись и очистку ваших буферов, нам также нужно очистить буферы ядра. как ? см. ниже: убедитесь, что вы также читаете мои комментарии в коде!

#include <unistd.h>
#include <stdio.h>

int main(int argc, char const *argv[]) {
  FILE * fd;
  fd = fopen("test_file.txt", "a");
  fprintf(fd, "String 1\n");
  fprintf(fd, "String 2\n");
  
  if(fclose(fd) != 0){
/*********************************************************************
fclose failed and errno will be set to endicate the error. be aware 
that we shall 
not call fsync or any other stream manipulation on fd in this case 
because we will get undefined behavior !!!
**********************************************************************/
      ... do work but no more work on this stream (fd)...
  }

  /* ~~ be aware that this is a blocking call ! (see details below) ~~*/
  if(fsync(fd) == -1){
      /*fsync fails*/
      ....
  } 
  return 0;
}

fsync() передает ("flushes") все измененные внутренние данные (т. е. измененные страницы буферного кэша) файла, на который ссылается файловый дескриптор fd, на дисковое устройство (или другое постоянное запоминающее устройство), чтобы можно было извлечь всю измененную информацию < strong>даже в случае сбоя или перезагрузки системы. Это включает в себя запись или очистку дискового кеша, если он есть. The call blocks пока устройство не сообщит о завершении передачи

Еще одна важная вещь: всегда лучше звонить по номеру fsync(fd), чем по номеру void sync(void);! Почему ?

потому что sync() приводит к тому, что все ожидающие изменения метаданных файловой системы и данных кэшированных файлов записываются в базовые файловые системы. обычно мы не хотим такого поведения (и это заставляет ядро ​​выполнять дополнительную работу, которая не является существенной! так что не вызывайте это.

эта страница и ответ недостаточно велики, чтобы включить все подробности, особые случаи и все коды ошибок! пожалуйста, обратитесь к справочным страницам и ссылкам ниже:

fclose

fsync

person nor    schedule 02.07.2020
comment
@piet_lu - я был бы рад, если бы вы проголосовали и приняли мой ответ, если сочтете его полезным (нажмите стрелку вверх и V рядом с началом ответа. спасибо). - person nor; 02.07.2020
comment
fsync принимает дескриптор файла, а не указатель FILE. Кроме того, в современных файловых системах с копированием при записи fsync в конечном итоге сбрасывает весь путь до корня, поскольку обновления метаданных попадают в шторм. По этой причине он фактически не работает на zfs, hsfs и многих других. - person mevets; 03.07.2020