Яке обґрунтування того, як Fread / Fwrite приймає розмір і зараховується як аргументи?


96

Ми тут на роботі обговорили, чому fread та fwrite беруть розмір для кожного члена та підраховують та повертають кількість прочитаних / написаних членів, а не просто беручи буфер та розмір. Єдине використання для нього, яке ми могли б придумати, це якщо ви хочете прочитати / записати масив структур, які не рівномірно діляться на вирівнюванні платформи і, отже, були забиті, але це не може бути настільки загальним, щоб виправдати цей вибір в дизайні.

З FREAD (3) :

Функція fread () зчитує елементи nmemb даних, довжиною кожного байта, з потоку, на який вказує потік, зберігаючи їх у місці, заданому ptr.

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

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


10
привіт це гарне запитання. я завжди дивувався про це
Йоханнес Шауб - літ

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
Це дало б чудову відповідь, якби обґрунтування було завершено.
Matt Joiner

13

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

Багато файлових систем базувалися на записах, отже, щоб ефективно задовольнити такі файлові системи, вам доведеться вказати кількість елементів ("записів"), що дозволяє fwrite / fread працювати на сховищі як записи, а не лише потоки байтів.


1
Я рада, що хтось це вигадав. Я багато працював із специфікаціями файлової системи та FTP, а записи / сторінки та інші концепції блокування дуже надійно підтримуються, хоча ніхто більше не використовує ці частини специфікацій.
Matt Joiner

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(...))замість того, щоб порівнювати результат із запитаною кількістю байт (для чого потрібні додатковий код С та додатковий машинний код).
R .. GitHub STOP HELPING ICE

1
@R .. Тільки не забудьте перевірити, що розмір * 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)і користувач набирає newline-ctrl-D після введення 18000 байт, було б непогано, якби функція могла повернути перші 10 000 байт, залишивши 8000 в очікуванні для майбутніх менших запитів на читання, але чи є будь-які реалізації, де це могло б статися? Де буде зберігатися 8000 байт до тих майбутніх запитів?
Supercat

Щойно перевіривши його, виявляється, що fread () працює не так, як я вважав би найзручнішим способом у цьому відношенні, але тоді заправка байтів назад у буфер читання після визначення короткого зчитування, можливо, трохи більше, ніж слід було очікувати від стандартні функції бібліотеки в будь-якому випадку. fread () буде читати часткові записи і записувати їх у буфер, але повернене значення вказуватиме, скільки повних записів було прочитано, і нічого не говорить (що мене досить дратує) про будь-які короткі читання, зняті stdin.
Пегас Епсілон,

... продовження ... Найкраще, що ви можете зробити, це, мабуть, заповнити ваш буфер читання нулями до fread і перевірити запис після того, як fread () каже, що закінчився, для будь-яких ненульових байтів. Особливо не допомагає вам, коли ваші записи можуть містити нуль, але якщо ви збираєтеся використовувати sizeбільше 1, ну ... Для запису можуть також бути ioctls або інші дурниці, які ви можете застосувати до потоку, щоб зробити його поводитись інакше, я не заглиблювався так глибоко.
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 низькі байти елемента, що б ви зробили?

У деяких системах (через вирівнювання) ви не можете отримати доступ до одного байта цілого числа без створення копії та зсуву.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.