The point of fread/fwrite is to write N elements, each of size S bytes. The API is not a simple 'write this number of bytes' interface.
Thus, for example:
struct S { int a, b; float c; };
struct S stuff[92];
fwrite(stuff, sizeof (struct S), 92, stream);
(I would not write '92' in real code, but I want the simple formulation in this example)
Thus it has two size values because it needs them to express the intended operation.
(I don't much like the API, but there it is)
It is not that this is arranged just in case one size_t value can't handle the overall size to read/write, because size_t is by definition large enough to hold the size of the largest possible object (it’s defined as “the unsigned integral type of the result of the sizeof
operator”) — and anything fread/fwrite can handle is a single contiguous object. To put it another way, given my above code fragement.
size_t sz = sizeof stuff;
is guaranteed to be valid.
In the Rationale for ANSI-X3.159-1989 (the document accompanying the standard that explains why decisions were made). it says
size_t is the appropriate type both for an object size and for an array bound, so this is the type of size and nelem.
Now, they were standardizing an existing function, not inventing it, and this just says why the type is now size_t rather than, say, int. But it's clear they were thinking in terms of reading and writing an array.
calloc
also takes 2size_t
parameters. To me these all feel like a nod to the fixed-sized record orientation of file systems of other operating systems that were in existence when UNIX was created.size_t
-sized argument, the API allows to theoretically transfer SIZE_T_MAX * SIZE_T_MAX bytes. Because you can't define a buffer of that size, that assumption is wrong.