Cơ sở lý luận cho việc fread / fwrite lấy kích thước và tính là đối số là gì?


96

Chúng tôi đã có một cuộc thảo luận tại đây về việc tại sao fread và fwrite lại lấy kích thước cho mỗi thành viên và đếm và trả về số lượng thành viên được đọc / ghi thay vì chỉ lấy bộ đệm và kích thước. Cách sử dụng duy nhất cho nó mà chúng tôi có thể đưa ra là nếu bạn muốn đọc / ghi một mảng cấu trúc không chia hết cho sự liên kết nền tảng và do đó đã được đệm nhưng điều đó không thể phổ biến đến mức đảm bảo sự lựa chọn này trong thiết kế.

Từ FREAD (3) :

Hàm fread () đọc các phần tử nmemb của dữ liệu, mỗi phần tử có kích thước byte dài, từ luồng được trỏ đến từng luồng, lưu trữ chúng tại vị trí được cho bởi ptr.

Hàm fwrite () ghi các phần tử nmemb của dữ liệu, mỗi phần tử có kích thước là byte, vào luồng được trỏ đến theo luồng, lấy chúng từ vị trí được cho bởi ptr.

fread () và fwrite () trả về số lượng mục được đọc hoặc ghi thành công (tức là không phải số ký tự). Nếu xảy ra lỗi hoặc đạt đến phần cuối của tệp, giá trị trả về là số lượng mục ngắn (hoặc bằng không).


10
này, đây là một câu hỏi hay. tôi luôn tự hỏi về điều đó
Johannes Schaub - litb

Câu trả lời:


22

Nó dựa trên cách thực hiện fread .

Thông số UNIX Đơn cho biết

Đối với mỗi đối tượng, các lệnh gọi kích thước sẽ được thực hiện tới hàm fgetc () và kết quả được lưu trữ, theo thứ tự được đọc, trong một mảng các ký tự không dấu phủ chính xác đối tượng.

fgetc cũng có ghi chú này:

Vì fgetc () hoạt động trên byte, việc đọc một ký tự bao gồm nhiều byte (hoặc "một ký tự nhiều byte") có thể yêu cầu nhiều lệnh gọi đến fgetc ().

Tất nhiên, điều này có trước các mã hóa ký tự byte biến đổi ưa thích như UTF-8.

SUS lưu ý rằng điều này thực sự được lấy từ các tài liệu ISO C.


72

Sự khác biệt giữa fread (buf, 1000, 1, stream) và fread (buf, 1, 1000, stream) là, trong trường hợp đầu tiên, bạn chỉ nhận được một đoạn 1000 byte hoặc nuthin, nếu tệp nhỏ hơn và trong trường hợp thứ hai, bạn nhận được mọi thứ trong tệp nhỏ hơn và lên đến 1000 byte.


4
Mặc dù đúng, điều đó chỉ nói lên một phần nhỏ của câu chuyện. Sẽ tốt hơn nếu so sánh một thứ gì đó đang đọc, chẳng hạn như một mảng các giá trị int hoặc một mảng cấu trúc.
Jonathan Leffler

3
Đây sẽ là một câu trả lời tuyệt vời nếu việc biện minh được hoàn thành.
Matt Joiner

13

Đây là suy đoán thuần túy, tuy nhiên trước đây (Một số vẫn còn tồn tại) nhiều hệ thống tệp không phải là các luồng byte đơn giản trên ổ cứng.

Nhiều hệ thống tệp được dựa trên bản ghi, do đó, để đáp ứng các hệ thống tệp như vậy một cách hiệu quả, bạn sẽ phải chỉ định số lượng mục ("bản ghi"), cho phép fwrite / fread hoạt động trên bộ lưu trữ dưới dạng bản ghi, không chỉ luồng byte.


1
Tôi rất vui vì ai đó đã đưa ra điều này. Tôi đã làm rất nhiều việc với các thông số kỹ thuật của hệ thống tệp và FTP và các bản ghi / trang và các khái niệm chặn khác được hỗ trợ rất chắc chắn, mặc dù không ai sử dụng các phần đó của thông số kỹ thuật nữa.
Matt Joiner

9

Ở đây, hãy để tôi sửa các chức năng đó:

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

Về cơ sở lý luận cho các tham số cho fread()/ fwrite(), tôi đã đánh mất bản sao K&R từ lâu nên tôi chỉ có thể đoán. Tôi nghĩ rằng câu trả lời có khả năng xảy ra là Kernighan và Ritchie có thể nghĩ đơn giản rằng việc thực hiện I / O nhị phân sẽ được thực hiện một cách tự nhiên nhất trên các mảng đối tượng. Ngoài ra, họ có thể nghĩ rằng khối I / O sẽ nhanh hơn / dễ thực hiện hơn hoặc bất cứ thứ gì trên một số kiến ​​trúc.

Mặc dù tiêu chuẩn C chỉ rõ điều đó fread()fwrite()được thực hiện theo nghĩa fgetc()fputc(), hãy nhớ rằng tiêu chuẩn đã ra đời rất lâu sau khi C được K&R định nghĩa và những thứ được chỉ định trong tiêu chuẩn có thể không nằm trong ý tưởng ban đầu của các nhà thiết kế. Thậm chí có thể những điều được nói trong "Ngôn ngữ lập trình C" của K&R có thể không giống như khi ngôn ngữ này được thiết kế lần đầu tiên.

Cuối cùng, đây là những gì PJ Plauger phải nói fread()trong "Thư viện C tiêu chuẩn":

Nếu sizeđối số (thứ hai) lớn hơn một, bạn không thể xác định liệu hàm có đọc đến size - 1các ký tự bổ sung ngoài những gì nó báo cáo hay không. Theo quy luật, bạn nên gọi hàm fread(buf, 1, size * n, stream);thay vì fread(buf, size, n, stream);

Về cơ bản, anh ấy nói rằng fread()giao diện của nó đã bị hỏng. Đối với fwrite()anh ấy lưu ý rằng, "Lỗi viết thường rất hiếm, vì vậy đây không phải là một thiếu sót lớn" - một tuyên bố mà tôi không đồng ý.


17
Trên thực tế, tôi thường thích làm theo cách khác: fread(buf, size*n, 1, stream);Nếu việc đọc không đầy đủ là một điều kiện lỗi, sẽ đơn giản hơn để sắp xếp freadtrả về 0 hoặc 1 thay vì số byte được đọc. Sau đó, bạn có thể làm những việc như if (!fread(...))thay vì phải so sánh kết quả với số byte được yêu cầu (yêu cầu thêm mã C và mã máy bổ sung).
R .. GitHub DỪNG TRỢ GIÚP

1
@R .. Chỉ cần đảm bảo kiểm tra kích thước * đếm! = 0 ngoài! Fread (...). Nếu size * count == 0, bạn sẽ nhận được giá trị trả về 0 khi đọc thành công (trong số 0 byte), feof () và ferror () sẽ không được đặt và errno sẽ là một cái gì đó vô nghĩa như ENOENT hoặc tệ hơn , một cái gì đó gây hiểu lầm (và có thể vi phạm nghiêm trọng) như EAGAIN - rất khó hiểu, đặc biệt là vì về cơ bản không có tài liệu nào la hét điều này với bạn.
Pegasus Epsilon

3

Có khả năng nó quay trở lại cách mà tệp I / O đã được triển khai. (trước đây) Có thể nhanh hơn khi ghi / đọc các tệp trong các khối sau đó viết mọi thứ cùng một lúc.


Không hẳn vậy. Đặc tả C cho fwrite lưu ý rằng nó thực hiện các cuộc gọi lặp đi lặp lại đến fputc
Powerlord

1

Có các đối số riêng biệt cho kích thước và số lượng có thể có lợi khi triển khai có thể tránh đọc bất kỳ bản ghi từng phần nào. Nếu một người sử dụng các lần đọc byte đơn từ một cái gì đó giống như một đường ống, ngay cả khi một người đang sử dụng dữ liệu định dạng cố định, người ta sẽ phải cho phép khả năng bản ghi bị tách thành hai lần đọc. Nếu có thể thay thế yêu cầu, ví dụ: đọc không chặn lên đến 40 bản ghi, mỗi bản ghi 10 byte khi có sẵn 293 byte và yêu cầu hệ thống trả về 290 byte (29 bản ghi toàn bộ) trong khi để lại 3 byte sẵn sàng cho lần đọc tiếp theo, điều đó sẽ thuận tiện hơn nhiều.

Tôi không biết các triển khai của fread có thể xử lý ngữ nghĩa như vậy ở mức độ nào, nhưng chúng chắc chắn có thể hữu ích cho các triển khai có thể hứa hẹn hỗ trợ chúng.


@PegasusEpsilon: Nếu ví dụ như một chương trình thực hiện fread(buffer, 10000, 2, stdin)và người dùng nhập newline-ctrl-D sau khi nhập 18.000 byte, sẽ thật tuyệt nếu hàm có thể trả về 10.000 byte đầu tiên trong khi vẫn để 8.000 byte còn lại đang chờ xử lý cho các yêu cầu đọc nhỏ hơn trong tương lai, nhưng có bất kỳ triển khai nào mà điều đó sẽ xảy ra? 8.000 byte sẽ được lưu trữ ở đâu khi chờ các yêu cầu trong tương lai?
supercat

Vừa mới kiểm tra nó, hóa ra fread () không hoạt động theo cách mà tôi cho là thuận tiện nhất về mặt này, nhưng sau đó việc nhồi các byte trở lại bộ đệm đọc sau khi xác định một lần đọc ngắn có lẽ nhiều hơn một chút so với chúng ta mong đợi. chức năng thư viện tiêu chuẩn dù sao. fread () sẽ đọc từng phần bản ghi và đẩy chúng vào bộ đệm, nhưng giá trị trả về sẽ chỉ định bao nhiêu bản ghi hoàn chỉnh đã được đọc và không cho bạn biết gì (điều này khá khó chịu đối với tôi) về bất kỳ lần đọc ngắn nào bị kéo ra khỏi stdin.
Pegasus Epsilon

... tiếp tục ... Tốt nhất bạn có thể làm là điền vào bộ đệm đọc của bạn bằng null trước khi fread và kiểm tra bản ghi sau khi fread () cho biết nó đã hoàn thành cho bất kỳ byte nào không phải null. Đặc biệt không giúp bạn khi bản ghi của bạn có thể chứa null, nhưng nếu bạn định sử dụng sizelớn hơn 1, thì ... Đối với bản ghi, cũng có thể có ioctls hoặc những thứ vô nghĩa khác mà bạn có thể áp dụng cho luồng để tạo nó cư xử khác đi, tôi chưa tìm hiểu sâu về điều đó.
Pegasus Epsilon

Ngoài ra, tôi đã xóa bình luận trước đó của mình do không chính xác. Ồ tốt.
Pegasus Epsilon

@PegasusEpsilon: C được sử dụng trên rất nhiều nền tảng, có các hành vi khác nhau. Khái niệm rằng các lập trình viên nên mong đợi sử dụng các tính năng và đảm bảo giống nhau trên tất cả các triển khai đã bỏ qua tính năng tốt nhất của C: rằng thiết kế của nó sẽ cho phép các lập trình viên sử dụng các tính năng và đảm bảo trên các nền tảng mà chúng có sẵn. Một số loại luồng có thể hỗ trợ các luồng phản hồi có kích thước tùy ý một cách dễ dàng và freadcông việc như bạn đã mô tả trên các luồng như vậy sẽ hữu ích nếu có một số cách để xác định các luồng hoạt động theo kiểu đó.
supercat

0

Tôi nghĩ rằng đó là bởi vì C thiếu chức năng quá tải. Nếu có một số, kích thước sẽ là dư thừa. Nhưng trong C, bạn không thể xác định kích thước của một phần tử mảng, bạn phải chỉ định một phần tử.

Xem xét điều này:

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

Nếu fwrite số byte được chấp nhận, bạn có thể viết như sau:

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

Nhưng nó chỉ là không hiệu quả. Bạn sẽ có số lần gọi hệ thống nhiều hơn sizeof (int).

Một điểm khác cần lưu ý là bạn thường không muốn một phần của phần tử mảng được ghi vào tệp. Bạn muốn toàn bộ số nguyên hoặc không có gì. fwrite trả về một số phần tử được viết thành công. Vì vậy, nếu bạn phát hiện ra rằng chỉ có 2 byte thấp của một phần tử được viết, bạn sẽ làm gì?

Trên một số hệ thống (do căn chỉnh), bạn không thể truy cập một byte của một số nguyên mà không tạo một bản sao và dịch chuyển.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.