Jakie jest uzasadnienie, aby fread / fwrite przyjmowało rozmiar i liczył jako argumenty?

96

Prowadziliśmy tutaj dyskusję na temat tego, dlaczego fread i fwrite przyjmują rozmiar na członka i liczą i zwracają liczbę elementów członkowskich odczytanych / zapisanych, a nie tylko przyjmować bufor i rozmiar. Jedynym zastosowaniem, jakie możemy wymyślić, jest to, że chcesz odczytać / zapisać tablicę struktur, które nie są równo podzielne przez wyrównanie platformy i dlatego zostały wypełnione, ale to nie może być tak powszechne, aby uzasadniać ten wybór w projektowaniu.

Z FREAD (3) :

Funkcja fread () odczytuje nmemb elementy danych, każdy rozmiar w bajtach, ze strumienia wskazywanego przez stream, przechowując je w lokalizacji podanej przez ptr.

Funkcja fwrite () zapisuje nmemb elementów danych, każdy rozmiar w bajtach, do strumienia wskazywanego przez stream, uzyskując je z lokalizacji podanej przez ptr.

fread () i fwrite () zwracają liczbę pomyślnie odczytanych lub zapisanych elementów (tj. nie liczbę znaków). Jeśli wystąpi błąd lub osiągnięto koniec pliku, zwracaną wartością jest krótka liczba elementów (lub zero).

David Holm
źródło
10
hej, to dobre pytanie. zawsze się nad tym zastanawiałem
Johannes Schaub - litb

Odpowiedzi:

22

Opiera się na sposobie implementacji fread .

Pojedyncza specyfikacja UNIX mówi

Dla każdego obiektu wywołania size powinny zostać wywołane do funkcji fgetc (), a wyniki zapisane, w kolejności odczytu, w tablicy bez znaku, dokładnie pokrywającej obiekt.

fgetc ma również tę notatkę:

Ponieważ fgetc () działa na bajtach, odczytanie znaku składającego się z wielu bajtów (lub „znaku wielobajtowego”) może wymagać wielu wywołań funkcji fgetc ().

Oczywiście poprzedza to fantazyjne kodowanie znaków o zmiennej bajtach, takie jak UTF-8.

SUS zauważa, że ​​jest to faktycznie zaczerpnięte z dokumentów ISO C.

Władca
źródło
72

Różnica w fread (buf, 1000, 1, stream) i fread (buf, 1, 1000, stream) polega na tym, że w pierwszym przypadku otrzymujesz tylko jedną porcję 1000 bajtów lub nic, jeśli plik jest mniejszy i ma W drugim przypadku otrzymujesz wszystko w pliku mniej niż i do 1000 bajtów.

Peter Miehle
źródło
4
Chociaż prawda, to tylko niewielka część historii. Byłoby lepiej, gdyby coś czytało, powiedzmy, tablicę wartości typu int lub tablicę struktur.
Jonathan Leffler
3
To byłaby świetna odpowiedź, gdyby uzasadnienie zostało zakończone.
Matt Joiner,
13

To czyste spekulacje, jednak dawno temu (niektóre wciąż istnieją) wiele systemów plików nie było zwykłymi strumieniami bajtów na dysku twardym.

Wiele systemów plików było opartych na rekordach, więc aby zapewnić takie systemy plików w efektywny sposób, będziesz musiał określić liczbę elementów („rekordów”), dzięki czemu fwrite / fread będą działać w pamięci jako rekordy, a nie tylko strumienie bajtów.

nr
źródło
1
Cieszę się, że ktoś o tym wspomniał. Wykonałem dużo pracy ze specyfikacjami systemu plików i FTP, a rekordy / strony i inne koncepcje blokowania są bardzo mocno obsługiwane, chociaż nikt już nie używa tych części specyfikacji.
Matt Joiner,
9

Tutaj, pozwól mi naprawić te funkcje:

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

Jeśli chodzi o uzasadnienie parametrów do fread()/fwrite() , to już dawno zgubiłem kopię K&R, więc mogę się tylko domyślać. Myślę, że prawdopodobną odpowiedzią jest to, że Kernighan i Ritchie mogli po prostu pomyśleć, że wykonywanie binarnych operacji we / wy byłoby najbardziej naturalne na tablicach obiektów. Mogli także pomyśleć, że blokowe wejścia / wyjścia będą szybsze / łatwiejsze do zaimplementowania lub cokolwiek innego na niektórych architekturach.

Mimo że norma C to określa fread()i fwrite()powinna zostać wdrożona w zakresie fgetc()i fputc(), należy pamiętać, że norma ta powstała długo po zdefiniowaniu C przez K&R i że rzeczy określone w normie mogły nie znajdować się w oryginalnych pomysłach projektantów. Jest nawet możliwe, że rzeczy powiedziane w „Języku programowania C” firmy K&R mogą nie być takie same, jak przy pierwszym projektowaniu języka.

Na koniec, oto, co PJ Plauger ma do powiedzenia fread()w „The Standard C Library”:

Jeśli size(drugi) argument jest większy niż jeden, nie można określić, czy funkcja odczytuje również size - 1dodatkowe znaki poza tym, co zgłasza. Z reguły lepiej jest wywoływać funkcję jako fread(buf, 1, size * n, stream);zamiast fread(buf, size, n, stream);

Zasadniczo mówi, że fread()interfejs jest uszkodzony. Dla fwrite()zauważa, że „błędy zapisu są zazwyczaj rzadkie, więc nie jest to poważny mankament” - stwierdzenie, że nie zgodzi się.

Michael Burr
źródło
17
Właściwie często lubię to robić w inny sposób: fread(buf, size*n, 1, stream);jeśli niekompletne odczyty są stanem błędu, łatwiej freadjest po prostu zwrócić 0 lub 1 zamiast liczby odczytanych bajtów. Następnie możesz zrobić takie rzeczy, jak if (!fread(...))zamiast porównywać wynik z żądaną liczbą bajtów (co wymaga dodatkowego kodu C i dodatkowego kodu maszynowego).
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
1
@R .. Po prostu upewnij się, że oprócz! Fread (...) sprawdź ten rozmiar * count! = 0. Jeśli size * count == 0, otrzymujesz zerową wartość zwracaną po pomyślnym odczycie (zero bajtów), feof () i ferror () nie zostaną ustawione, a errno będzie czymś bezsensownym, jak ENOENT lub gorzej , coś wprowadzającego w błąd (i prawdopodobnie krytycznie przełamującego), jak EAGAIN - bardzo zagmatwane, zwłaszcza, że ​​w zasadzie żadna dokumentacja nie krzyczy, że to cię dostało.
Pegasus Epsilon
3

Prawdopodobnie wraca do sposobu implementacji operacji we / wy pliku. (dawno temu) Mogło być szybsze zapisywanie / odczytywanie plików w blokach niż zapisywanie wszystkiego naraz.

dolch
źródło
Nie całkiem. Specyfikacja C dla fwrite zauważa, że ​​wykonuje powtarzające się wywołania fputc: opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord
1

Posiadanie oddzielnych argumentów dla rozmiaru i liczby może być korzystne w implementacji, która pozwala uniknąć odczytywania częściowych rekordów. Gdyby ktoś używał odczytów jednobajtowych z czegoś takiego jak potok, nawet gdyby korzystał z danych o stałym formacie, należałoby uwzględnić możliwość rozdzielenia rekordu na dwa odczyty. Gdyby zamiast tego mógł zażądać np. Nieblokującego odczytu do 40 rekordów po 10 bajtów każdy, gdy dostępne są 293 bajty, i kazać systemowi zwrócić 290 bajtów (29 całych rekordów), pozostawiając 3 bajty gotowe do następnego odczytu, będzie znacznie wygodniejszy.

Nie wiem, w jakim stopniu implementacje fread radzą sobie z taką semantyką, ale z pewnością mogą być przydatne w przypadku implementacji, które mogłyby je wspierać.

supercat
źródło
@PegasusEpsilon: Jeśli np. Program to robi, fread(buffer, 10000, 2, stdin)a użytkownik wpisze nową linię -ctrl-D po wpisaniu 18000 bajtów, byłoby miło, gdyby funkcja mogła zwrócić pierwsze 10000 bajtów, pozostawiając pozostałe 8000 oczekujące na przyszłe mniejsze żądania odczytu, ale czy są jakieś wdrożenia, w których to by się stało? Gdzie byłoby przechowywanych 8000 bajtów w oczekiwaniu na te przyszłe żądania?
supercat
Po przetestowaniu tego, okazuje się, że fread () nie działa w sposób, który uznałbym za najwygodniejszy pod tym względem, ale potem upychanie bajtów z powrotem do bufora odczytu po ustaleniu krótkiego odczytu to prawdopodobnie trochę więcej niż powinniśmy się spodziewać i tak standardowe funkcje biblioteczne. fread () odczyta częściowe rekordy i umieści je w buforze, ale zwracana wartość określi, ile kompletnych rekordów zostało przeczytanych, i nie mówi nic (co jest dla mnie dość denerwujące) o jakichkolwiek krótkich odczytach wykonanych ze standardowego wejścia.
Pegaz Epsilon
... ciąg dalszy ... Najlepsze, co możesz zrobić, to prawdopodobnie wypełnienie bufora odczytu wartościami zerowymi przed fread i sprawdzenie rekordu po tym, jak fread () mówi, że zakończył się, nie ma żadnych bajtów innych niż null. Nie pomaga ci szczególnie, gdy twoje rekordy mogą zawierać wartość null, ale jeśli zamierzasz użyć sizewięcej niż 1, cóż ... Dla przypomnienia, mogą również istnieć ioctls lub inne bzdury, które możesz zastosować do strumienia, aby to zrobić zachowuję się inaczej, nie zagłębiłem się tak głęboko.
Pegasus Epsilon
Usunąłem również mój wcześniejszy komentarz z powodu nieścisłości. No cóż.
Pegaz Epsilon
@PegasusEpsilon: C jest używany na wielu platformach, które obsługują różne zachowania. Pogląd, że programiści powinni spodziewać się używania tych samych funkcji i gwarancji we wszystkich implementacjach, ignoruje to, co było najlepszą cechą C: że jego konstrukcja umożliwi programistom korzystanie z funkcji i gwarancji na platformach, na których były one dostępne. Niektóre rodzaje strumieni mogą z łatwością obsługiwać odpowiedzi zwrotne o dowolnej wielkości, a freadpraca na takich strumieniach zgodna z opisem byłaby przydatna, gdyby istniał sposób na zidentyfikowanie strumieni, które działają w ten sposób.
supercat
0

Myślę, że to dlatego, że C nie ma przeciążenia funkcji. Gdyby tak było, rozmiar byłby zbędny. Ale w C nie możesz określić rozmiaru elementu tablicy, musisz go określić.

Rozważ to:

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

Jeśli fwrite zaakceptowaną liczbę bajtów, możesz napisać:

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

Ale to jest po prostu nieefektywne. Będziesz mieć sizeof (int) razy więcej wywołań systemowych.

Kolejną kwestią, którą należy wziąć pod uwagę, jest to, że zazwyczaj nie chcesz, aby część elementu tablicy była zapisywana w pliku. Chcesz całą liczbę całkowitą albo nic. fwrite zwraca liczbę pomyślnie zapisanych elementów. Więc jeśli odkryjesz, że zapisane są tylko 2 niskie bajty elementu, co byś zrobił?

W niektórych systemach (z powodu wyrównania) nie można uzyskać dostępu do jednego bajtu liczby całkowitej bez tworzenia kopii i przesuwania.

Vanuan
źródło