Каково обоснование того, что fread / fwrite принимает размер и учитывается в качестве аргументов?

На работе мы обсуждали, почему fread () и fwrite () принимают размер для каждого члена и подсчитывают и возвращают количество прочитанных / записанных членов, а не просто принимают буфер и размер. Единственное применение для него, которое мы могли бы придумать, - это если вы хотите читать / записывать массив структур, которые не делятся равномерно по выравниванию платформы и, следовательно, были дополнены, но это не может быть настолько распространено, чтобы гарантировать этот выбор. в дизайне.

Из fread (3):

Функция fread () считывает nmemb элементов данных, каждый размером в байтах, из потока, на который указывает stream, и сохраняет их в месте, указанном ptr.

Функция fwrite () записывает nmemb элементов данных, каждый размером в байтах, в поток, на который указывает stream, получая их из местоположения, заданного ptr.

fread () и fwrite () возвращают количество успешно прочитанных или записанных элементов (т. е. не количество символов). Если возникает ошибка или достигается конец файла, возвращаемое значение - это короткое количество элементов (или ноль).


person David Holm    schedule 17.11.2008    source источник
comment
эй, это хороший вопрос. я всегда задавался вопросом об этом   -  person Johannes Schaub - litb    schedule 17.11.2008
comment
Пожалуйста, ознакомьтесь с этой веткой: stackoverflow.com/questions/8589425 / how-does-fread-really-work   -  person Franken    schedule 15.04.2012


Ответы (7)


Это основано на том, как реализован fread.

В единой спецификации UNIX говорится

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

fgetc также содержит это примечание:

Поскольку fgetc () работает с байтами, чтение символа, состоящего из нескольких байтов (или «многобайтового символа»), может потребовать нескольких вызовов fgetc ().

Конечно, это предшествует причудливым кодировкам символов с переменными байтами, таким как UTF-8.

SUS отмечает, что это фактически взято из документов ISO C.

person Powerlord    schedule 17.11.2008

Разница между fread(buf, 1000, 1, stream) и fread(buf, 1, 1000, stream) заключается в том, что в первом случае вы получаете только один фрагмент размером 1000 байт или ничего, если файл меньше, а во втором случае вы получаете все в файле размером менее 1000 байт.

person Peter Miehle    schedule 17.11.2008
comment
Хотя это правда, это лишь небольшая часть истории. Лучше было бы что-то противопоставить чтению, скажем, массива значений типа int или массива структур. - person Jonathan Leffler; 18.11.2008
comment
Это был бы отличный ответ, если бы обоснование было завершено. - person Matt Joiner; 19.09.2010

Это чистые предположения, однако в те времена (некоторые все еще существуют) многие файловые системы не были простыми потоками байтов на жестком диске.

Многие файловые системы основаны на записях, поэтому для эффективного удовлетворения таких файловых систем вам нужно будет указать количество элементов («записей»), что позволит fwrite / fread работать с хранилищем как с записями, а не только с потоками байтов.

person nos    schedule 19.09.2010
comment
Я рад, что кто-то поднял это. Я много работал со спецификациями файловой системы, и FTP, записи / страницы и другие концепции блокировки очень надежно поддерживаются, хотя никто больше не использует эти части спецификаций. - person Matt Joiner; 19.09.2010

Вот, позвольте мне исправить эти функции:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

Что касается обоснования параметров _2 _ / _ 3_, я давно потерял свою копию K&R, поэтому могу только догадываться. Я думаю, что вероятный ответ заключается в том, что Керниган и Ричи могли просто подумать, что выполнение двоичного ввода-вывода будет наиболее естественно выполняться на массивах объектов. Кроме того, они могли подумать, что блочный ввод-вывод будет быстрее / проще реализовать или что-то еще на некоторых архитектурах.

Несмотря на то, что стандарт C определяет, что fread() и fwrite() должны быть реализованы в терминах fgetc() и fputc(), помните, что стандарт появился задолго после того, как C был определен K&R, и что вещи, указанные в стандарте, могли не входить в первоначальные идеи дизайнеров. Возможно даже, что вещи, сказанные в «Языке программирования C» K&R, могут быть не такими, как когда язык только разрабатывался.

Наконец, вот что П.Дж. Плогер говорит о fread() в "Стандартной библиотеке C":

Если size (второй) аргумент больше единицы, вы не можете определить, считала ли функция до size - 1 дополнительных символов сверх того, о чем она сообщает. Как правило, лучше вызывать функцию как fread(buf, 1, size * n, stream); вместо fread(buf, size, n, stream);

По сути, он говорит, что интерфейс fread() сломан. Что касается fwrite(), он отмечает, что «ошибки записи обычно редки, поэтому это не является серьезным недостатком» - с утверждением, с которым я бы не согласился.

person Michael Burr    schedule 17.11.2008
comment
На самом деле мне часто нравится делать это по-другому: fread(buf, size*n, 1, stream); Если неполное чтение является условием ошибки, проще сделать так, чтобы fread просто возвращал 0 или 1, а не количество прочитанных байтов. Затем вы можете делать такие вещи, как if (!fread(...)), вместо того, чтобы сравнивать результат с запрошенным количеством байтов (что требует дополнительного кода C и дополнительного машинного кода). - person R.. GitHub STOP HELPING ICE; 31.07.2010
comment
@R .. Только не забудьте проверить, что size * count! = 0 в дополнение к! Fread (...). Если size * count == 0, вы получаете нулевое возвращаемое значение при успешном чтении (нулевых байтов), feof () и ferror () не будут установлены, а errno будет что-то бессмысленное, например ENOENT, или того хуже, что-то вводящее в заблуждение (и, возможно, критически нарушающее), например EAGAIN - очень запутанное, тем более что в основном никакая документация не кричит вам об этом. - person Pegasus Epsilon; 27.03.2019

Вероятно, это восходит к тому, как был реализован файловый ввод-вывод. (в те времена) Возможно, было бы быстрее записывать / читать файлы блоками, чем записывать все сразу.

person dolch    schedule 17.11.2008
comment
Не совсем. В спецификации C для fwrite отмечается, что он выполняет повторные вызовы fputc: opengroup.org/ onlinepubs / 009695399 / functions / fwrite.html - person Powerlord; 17.11.2008

Наличие отдельных аргументов для размера и количества может быть выгодным для реализации, которая может избежать чтения каких-либо частичных записей. Если бы кто-то использовал однобайтовые чтения из чего-то вроде конвейера, даже если бы он использовал данные фиксированного формата, нужно было бы допустить возможность разделения записи на два чтения. Если бы вместо этого можно было запросить, например, неблокирующее чтение до 40 записей по 10 байтов каждая, когда доступно 293 байта, и получение системой 290 байтов (29 целых записей), оставляя 3 байта готовыми для следующего чтения, что было бы намного удобнее.

Я не знаю, в какой степени реализации fread могут обрабатывать такую ​​семантику, но они, безусловно, могут быть полезны в реализациях, которые могут обещать их поддерживать.

person supercat    schedule 11.08.2018
comment
@PegasusEpsilon: Если, например, программа выполняет fread(buffer, 10000, 2, stdin), а пользователь вводит новую строку-ctrl-D после ввода 18 000 байт, было бы неплохо, если бы функция могла возвращать первые 10 000 байт, оставляя оставшиеся 8 000 ожидающими для будущих меньших запросов чтения, но есть ли какие-либо реализации, где это может случиться? Где будут храниться 8000 байт в ожидании этих будущих запросов? - person supercat; 27.03.2019
comment
Только что протестировав его, оказалось, что fread () не работает тем способом, который я считаю наиболее удобным в этом отношении, но затем вставка байтов обратно в буфер чтения после определения короткого чтения, вероятно, немного больше, чем мы должны ожидать от в любом случае стандартные библиотечные функции. fread () будет читать частичные записи и помещать их в буфер, но возвращаемое значение будет указывать, сколько полных записей было прочитано, и ничего не сообщает вам (что меня довольно раздражает) о каких-либо коротких читает со стандартного ввода. - person Pegasus Epsilon; 29.03.2019
comment
... продолжение ... Лучшее, что вы можете сделать, это, вероятно, заполнить буфер чтения нулями перед fread и проверить запись после того, как fread () сообщает о завершении для любых ненулевых байтов. Это не особенно помогает, когда ваши записи могут содержать null, но если вы собираетесь использовать size больше 1, ну ... Для записи также могут быть ioctls или другая ерунда, которую вы можете применить к потоку, чтобы сделать он ведет себя по-другому, я не вникал так глубоко. - person Pegasus Epsilon; 29.03.2019
comment
Также я удалил свой предыдущий комментарий из-за неточности. Ну что ж. - person Pegasus Epsilon; 29.03.2019
comment
@PegasusEpsilon: C используется на очень многих платформах, которые допускают различное поведение. Представление о том, что программисты должны ожидать использования одних и тех же функций и гарантий во всех реализациях, игнорирует то, что было лучшей особенностью C: его дизайн позволяет программистам использовать функции и гарантии на тех платформах, где они доступны. Некоторые виды потоков могут легко поддерживать откаты произвольного размера, и fread работа с такими потоками, как вы описали, было бы полезно, если бы существовал какой-то способ идентифицировать потоки, которые работают таким образом. - person supercat; 30.03.2019
comment
Потоки ввода-вывода уже настолько выше абстракции файлового дескриптора, что попытки определить их поведение на основе того, что поддерживают дескрипторы файлов, лежащие в основе архитектуры, кажутся глупыми. Не говоря уже о том, что read () и fread () могут читать из поддерживаемого библиотекой буфера чтения, позволяя всякое дурачество с буфером, которое вам когда-либо понадобится. Но есть несколько вещей в C, с которыми я не согласен с точки зрения дизайна. Слишком поздно менять их сейчас. - person Pegasus Epsilon; 31.03.2019
comment
@PegasusEpsilon: Если некоторым программам потребуется выполнять задачи, на которые многие системы способны, а многие другие нет, то наличие стандартных средств выполнения этих задач на тех системах, которые могут их поддерживать, было бы более полезным. чем требование, чтобы каждая программа, которая поддерживается не на всех платформах, была индивидуально настроена для каждой отдельной реализации, поддерживающей требуемую семантику. - person supercat; 03.04.2019
comment
Вы понимаете, что ни одна система не имеет встроенной поддержки такой концептуальной вещи, как поток ФАЙЛОВ? Не говоря уже о том, что файловые системы, как правило, не являются аппаратными, идея потока - это изготовление целой стандартной библиотеки. Тот факт, что он ведет себя определенным образом, потому что не все оборудование поддерживает то, что мы предпочли бы, является полностью произвольным - вся концепция изобретена намного выше аппаратного уровня, они просто решили не писать спецификацию определенным образом. - person Pegasus Epsilon; 04.04.2019
comment
Примером места, где спецификация C требует неудобного домашнего кода, где другие системы могут изначально поддерживать то, что хочет C, является строка ASCIZ с завершающим нулем. DOS использует это повсеместно, но, как ни странно, не для вывода на консоль. Для вывода на консоль (и только для вывода на консоль, насколько я могу судить) прерывание 21h требует, чтобы строка заканчивалась символом $. C не преобразуется внезапно в использование строк с завершающим символом в DOS, однако требует, чтобы разработчики библиотеки C написали функцию put (), которая по-прежнему использует нулевой терминатор. - person Pegasus Epsilon; 04.04.2019
comment
Дело в том, что спецификация - это то, что есть в спецификации, и во многих случаях она должна выбирать: поддерживать ее везде, независимо от сложности на данной платформе, или не поддерживать ее, потому что в некоторых местах это было бы сложно. Ясно, что частичное чтение - это второе, в то время как put () явно первое, и выбор остается за каким-то парнем, который может или не может использовать логику (put () проще, чем мой гипотетический fread () для реализации, чтобы быть честным, но это также проще, чем fread () в его нынешнем виде, так что ...) принять такое решение. - person Pegasus Epsilon; 04.04.2019
comment
@PegasusEpsilon: Большинство авторов спецификаций признают третий вариант: распознавать то, что должны делать качественные реализации, когда это возможно. Многие среды исполнения имеют собственные концепции потоков, которые работают так же, как те, которые используются в fread, fopen и т. Д., И реализации C для таких сред часто пытаются минимизировать объем буферизации между потоками C и потоками среды. - person supercat; 04.04.2019

Я думаю, это потому, что в C отсутствует перегрузка функций. Если бы они были, размер был бы избыточным. Но в C вы не можете определить размер элемента массива, вы должны указать его.

Учти это:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

Если fwrite принимает количество байтов, вы можете написать следующее:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

Но это просто неэффективно. У вас будет в sizeof (int) раз больше системных вызовов.

Еще один момент, который следует принять во внимание, заключается в том, что обычно вы не хотите, чтобы часть элемента массива была записана в файл. Вам нужно целое число или ничего. fwrite возвращает количество успешно записанных элементов. Итак, если вы обнаружите, что записаны только 2 младших байта элемента, что бы вы сделали?

В некоторых системах (из-за выравнивания) вы не можете получить доступ к одному байту целого числа без создания копии и сдвига.

person Vanuan    schedule 18.04.2012