Есть ли гарантированный и безопасный способ обрезания файла с помощью указателя ANSI C FILE?

Я знаю, что ANSI C определяет fopen, fwrite, fread, fclose для изменения содержимого файла. Однако, когда дело доходит до обрезки файла, мы должны обратиться к конкретной функции ОС, например, truncate() в Linux, _chsize_s_() в Windows. Но прежде чем мы сможем назвать эти специфические функции ОС, мы должны получить файл-дескриптор из указателя FILE, вызвав fileno , а также не ANSI-C.

Мой вопрос: надежно ли продолжать использовать FILE* после усечения файла? Я имею в виду, что слой ANSI C FILE имеет свой собственный буфер и не знает, что файл усечен ниже. Если буферизованные байты находятся за пределами усеченной точки, будет ли буферизованное содержимое очищаться до файла при выполнении fclose() ?

Если нет гарантии, какова наилучшая практика использования функций ввода-вывода файлов, сопровождаемых усечением при записи переносной программы под Windows-Linux?

Аналогичный вопрос: при запросе размера файла из файла-дескриптора, возвращаемого fileno , является ли он точным размером, когда я позже позвоню fclose() – без дальнейшего fwrite() ?

[EDIT 2012-12-11]

Согласно предложению Джошуа. Я пришел к выводу, что текущая возможная setbuf(stream, NULL); практика: установите stream в небуферизованный режим, вызвав setbuf(stream, NULL); , то truncate() или _chsize_s() могут работать мирно с streamом.

Во всяком случае, ни один официальный документ явно не подтверждает это поведение, будь то Microsoft CRT или GNU glibc.

Путь POSIX ….

ftruncate() – это то, что вы ищете, и оно было в базовых спецификациях POSIX с 2001 года, поэтому оно должно быть в любой современной POSIX-совместимой системе.

Обратите внимание: ftruncate() работает с дескриптором файла POSIX (несмотря на его потенциально вводящее в заблуждение имя), а не с дескриптором streamа STDIO stream. Также обратите внимание, что операции микширования в streamе STDIO и на базовых вызовах ОС, которые работают с файловым дескриптором для открытого streamа, могут путать внутреннее состояние выполнения библиотеки STDIO.

Таким образом, для безопасного использования ftruncate() с помощью STDIO может потребоваться сначала сбросить любые буферы STDIO (с fflush() ), если ваша программа, возможно, уже записана в соответствующий stream. Это приведет к тому, что STDIO не попытается очистить в противном случае неписанный буфер до файла после завершения усечения.

Затем вы можете использовать fileno() в дескрипторе FILE streamа STDIO для поиска базового дескриптора файла для открытого streamа STDIO, и затем вы будете использовать этот файловый дескриптор с помощью ftruncate() . Возможно, вы fileno() поместить вызов fileno() прямо в список параметров для ftruncate() чтобы вы не поддерживали дескриптор файла и случайно использовали его, но другие способы, которые могли бы еще больше запутать внутреннее состояние STDIO. Возможно, так (скажем, усечь файл на текущее смещение streamа STDIO):

 /* * NOTE: fflush() is not needed here if there have been no calls to fseek() since * the last fwrite(), assuming it extended the length of the stream -- * ftello() will account for any unwritten buffers */ if (ftruncate(fileno(stdout), ftello(stdout)) == -1) { fprintf(stderr, "%s: ftruncate(stdout) failed: %s\n", argv[0], strerror(errno)); exit(1); } /* fseek() is not necessary here since we truncated at the current offset */ 

Также обратите внимание, что в определении POSIX функции ftruncate() указано: « Значение указателя поиска не может быть изменено вызовом функции ftruncate () », поэтому это означает, что вам также может понадобиться использовать fseek() для установки уровня STDIO ( и, следовательно, косвенно дескриптор файла) либо к новому концу файла, либо, возможно, вернуться к началу файла, или где-то еще в пределах файла файла, по желанию. (Обратите внимание, что fseek() не требуется, если точка усечения найдена с использованием ftello() .)

Вам не нужно будет делать stream STDIO небуферизованным, если вы выполните описанную выше процедуру, хотя, конечно, это может быть альтернативой использованию fflush() (но не fseek() ).

Без POSIX ….

Если вам нужно придерживаться строгого стандарта ISO C, скажем, C99, то у вас нет переносного способа обрезать файл с заданной длиной, отличной от нуля (0). Последний проект C11, о котором я рассказываю в разделе 7.21.3 (пункт 2):

Двоичные файлы не усекаются, кроме как определено в 7.21.5.3. Независимо от того, является ли запись в текстовом streamе связанным файлом с усечением за пределами этой точки, определяется реализация.

(и 7.21.5.3 описывает флаги fopen() которые позволяют обрезать файл до нулевой длины)

Оговорка о текстовых файлах существует, потому что в глупых системах, которые имеют как текстовые, так и двоичные файлы (в отличие от простых агностических файлов контента в стиле POSIX), часто можно записать значение в файл, который будет сохранен в файле в позиции, записанной и которая будет считаться индикатором EOF когда файл будет прочитан следующим образом.

Другие типы систем могут иметь разные базовые интерфейсы ввода-вывода файлов, которые не совместимы с POSIX, при этом обеспечивая совместимую библиотеку ISO C STDIO. Теоретически, если такая система предлагает что-то похожее на fileno() и ftrunctate() аналогичная процедура может быть использована и с ними, при условии, что вы принимали такую ​​же осторожность, чтобы избежать путаницы внутреннего состояния среды STDIO.

Что касается запроса размера файла ….

Вы также спрашивали, будет ли размер файла найденным путем запроса файлового дескриптора, возвращенного fileno (), будет точным представлением размера файла после успешного вызова функции fclose() , даже без дальнейших вызовов fwrite() .

Ответ: Не делай этого!

Как я уже упоминал выше, дескриптор файла POSIX для файла, открытого как stream STDIO, должен использоваться очень осторожно, если вы не хотите путать внутреннее состояние выполнения библиотеки STDIO. Мы можем добавить здесь, что важно не путать себя с этим.

Самый правильный способ найти текущий размер файла, открытого как stream STDIO, – это найти его конец, а затем спросить, где указатель streamа использует только функции STDIO.

Не является ли небуферизованная запись нулевых байтов, которые должны усекать файл в этой точке?

См. Этот вопрос, как установить небуферизованный: небуферизованный ввод-вывод в ANSI C