В чем причина того, что fread / fwrite принимает размер и считается аргументом?

96

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

Из FREAD (3) :

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

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

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

Дэвид Холм
источник
10
эй, это хороший вопрос. мне всегда было интересно об этом
Йоханнес Шауб - litb
1
Пожалуйста, ознакомьтесь с этой веткой: stackoverflow.com/questions/8589425/how-does-fread-really-work
Franken

Ответы:

22

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

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

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

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

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

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

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

Пауэрлорд
источник
72

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

Питер Миле
источник
4
Хотя это правда, это лишь небольшая часть истории. Лучше было бы что-то противопоставить чтению, скажем, массива значений типа int или массива структур.
Джонатан Леффлер
3
Это был бы отличный ответ, если бы оправдание было завершено.
Мэтт Джойнер
13

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

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

нет
источник
1
Я рад, что кто-то поднял это. Я много работал со спецификациями файловой системы и FTP, а записи / страницы и другие концепции блокировки очень надежно поддерживаются, хотя никто больше не использует эти части спецификаций.
Мэтт Джойнер
9

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

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);
}

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

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

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

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

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

Майкл Берр
источник
17
На самом деле мне часто нравится делать это по-другому: fread(buf, size*n, 1, stream);если неполное чтение является условием ошибки, проще организовать freadпросто возврат 0 или 1, а не количество прочитанных байтов. Затем вы можете делать такие вещи, как if (!fread(...))вместо того, чтобы сравнивать результат с запрошенным количеством байтов (что требует дополнительного кода C и дополнительного машинного кода).
R .. GitHub НЕ ПОМОГАЕТ ICE
1
@R .. Только не забудьте проверить, что size * count! = 0 в дополнение к! Fread (...). Если size * count == 0, вы получаете нулевое возвращаемое значение при успешном чтении (нулевых байтов), feof () и ferror () не будут установлены, а errno будет чем-то бессмысленным, например ENOENT или хуже , что-то вводящее в заблуждение (и, возможно, критически нарушающее), такое как EAGAIN - очень запутанное, тем более что в основном никакая документация не кричит вам об этом.
Pegasus Epsilon
3

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

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

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

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

суперкар
источник
@PegasusEpsilon: если, например, программа выполняет fread(buffer, 10000, 2, stdin)и пользователь вводит новую строку-ctrl-D после ввода 18 000 байт, было бы неплохо, если бы функция могла возвращать первые 10 000 байт, оставляя оставшиеся 8 000 ожидающими для будущих меньших запросов чтения, но есть ли какие-нибудь реализации, где это могло бы произойти? Где будут храниться 8000 байт в ожидании этих будущих запросов?
supercat
Только что протестировав его, оказалось, что fread () не работает тем способом, который я считаю наиболее удобным в этом отношении, но затем вставка байтов обратно в буфер чтения после определения короткого чтения, вероятно, немного больше, чем мы должны ожидать от в любом случае стандартные библиотечные функции. fread () будет читать частичные записи и помещать их в буфер, но возвращаемое значение будет указывать, сколько полных записей было прочитано, и ничего не сообщает вам (что меня довольно раздражает) о любых коротких чтениях, выполненных со стандартного ввода.
Pegasus Epsilon
... продолжение ... Лучшее, что вы можете сделать, это, вероятно, заполнить буфер чтения нулями перед fread и проверить запись после того, как fread () сообщает о завершении для любых ненулевых байтов. Это не особенно помогает, когда ваши записи могут содержать null, но если вы собираетесь использовать sizeбольше 1, ну ... Для записи также могут быть ioctls или другая ерунда, которую вы можете применить к потоку, чтобы сделать это веди себя по-другому, я не вникал так глубоко.
Pegasus Epsilon
Также я удалил свой предыдущий комментарий из-за неточности. Ну что ж.
Pegasus Epsilon
@PegasusEpsilon: C используется на многих платформах, которые допускают различное поведение. Представление о том, что программисты должны ожидать использования одних и тех же функций и гарантий во всех реализациях, игнорирует то, что было лучшей особенностью C: его дизайн позволяет программистам использовать функции и гарантии на платформах, где они доступны. Некоторые виды потоков могут легко поддерживать откаты произвольного размера, и freadработа, которую вы описали с такими потоками, была бы полезна, если бы существовал какой-то способ идентифицировать потоки, которые работают таким образом.
supercat
0

Я думаю, это потому, что в 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 младших байта элемента, что бы вы сделали?

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

Вануан
источник