> I agree it's a weird API. Not sure where it comes from, maybe some '70s thing with reading blocks?
Probably, yes. In the earliest versions of C, stdio hadn’t been invented yet, and all IO was done using Unix file descriptors - which was fine, because C only ran on Unix. But, they were keen to prove the language was cross-platform, and Bell Labs used both IBM and Honeywell mainframes, so they started porting C to those two platforms, as a proof-of-concept of its portability. And there they ran into a big problem-mainframe IO was radically different from Unix, and the file descriptor API was rather ill-suited to it. So, Mike Lesk invented a “portable IO package”, which abstracted over the differences between mainframe and Unix IO-and that package was the ancestor of stdio.
And I think that explains why fread() is designed the way it is. IBM mainframe IO is fundamentally record-oriented [0] (and probably Honeywell was too, although I know far less about it). To Unix, reading 10 records of 80 bytes each, 80 records of 10 bytes each, or 1 record of 800 bytes each - the three are largely equivalent. Not so under MVS-if, when you create a file, you declare it as being composed of 80 byte fixed length records, the OS will force all reads/writes to be multiples of 80 bytes-hence why fread() makes you declare the record size, since it is much more essential information on that platform.
[0] Historically speaking; nowadays z/OS, the successor to MVS, supports Unix-style byte-oriented IO as well as classic mainframe record-oriented IO; but back in the 70s, those Unix compatibility features were still 20 years away. And classic mainframe apps still predominantly rely on record-based IO