2

Consider these library functions:

void *calloc(size_t num, size_t size);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

My question here is if I can safely switch some arguments. Is this:

calloc(1, 8);
fwrite(p, 1, 8, s);

the same as this?

calloc(8, 1);
fwrite(p, 8, 1, s);

Yes, I'm not using the return values. That's not the point here. My question is simply if it matters at all if I switch the argument for number of elements and the argument for element size. There are probably more library functions where this apply, so don't restrict yourself to only the two examples I gave.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
klutt
  • 30,332
  • 17
  • 55
  • 95
  • 2
    [No for `calloc`](https://stackoverflow.com/questions/501839/is-calloc4-6-the-same-as-calloc6-4) Yes for `fwrite` because it affects the return value but not if you ignore it, which you should not. – Eric Postpischil Feb 13 '21 at 14:47
  • 1
    From my VC2008 documentation: "The calloc function allocates storage space for an array of _num_ elements, each of length _size_ bytes." This would imply the elements are consecutive and not padded. Then the two calls are the same. – Paul Ogilvie Feb 13 '21 at 14:50

2 Answers2

2

Both calloc(), fread and fwrite() will attempt the same work with count and size arguments swapped. Incidentally these arguments are specified in a different order for fread/fwrite and calloc:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
void *calloc(size_t nmemb, size_t size);

While it makes no difference for calloc(), transposing the arguments in fread() and fwrite() has an effect on the return value, which is the number of elements successfully read or written. Make sure you use the correct values and compare the return value with the nmemb argument (the third argument):

#include <stdio.h>

struct chunk {
    int a, b, c;
};

size_t copy_chunks(FILE *from, FILE *to) {
    struct chunk array[64];
    size_t total = 0, nread, nwritten;
    while ((nread = fread(array, sizeof(*array), 64)) != 0) {
        nwritten = fread(array, sizeof(*array), nread);
        total += nwritten;
        if (nwritten != nread) {
            fprintf(stderr, "write error: wrote %zu of %zu elements\n", nwritten, nread);
            break;
    }
    return total;
}

Note that it is indeterminate how many bytes were actually written in case of write error above.

Here is the text from the C Standard:

7.21.8.2 The fwrite function

Synopsis

 size_t fwrite(const void * restrict ptr,
               size_t size, size_t nmemb,
               FILE * restrict stream);

The fwrite function writes, from the array pointed to by ptr, up to nmemb elements whose size is specified by size, to the stream pointed to by stream. For each object, size calls are made to the fputc function, taking the values (in order) from an array of unsigned char exactly overlaying the object. The file position indicator for the stream (if defined) is advanced by the number of characters successfully written. If an error occurs, the resulting value of the file position indicator for the stream is indeterminate.

Returns

The fwrite function returns the number of elements successfully written, which will be less than nmemb only if a write error is encountered. If size or nmemb is zero, fwrite returns zero and the state of the stream remains unchanged.

If you do not care about the return value, it makes no difference indeed... but ignoring errors can make a difference depending on the consequences of unexpected circumstances. Also think about later readers of your code and make it easier for them to understand what it does by using the proper arguments.

Other library functions take a void * pointer, a size and a count argument and the behavior is deeply affected if not undefined if you pass them in the wrong order:

void *bsearch(const void *key, const void *base,
              size_t nmemb, size_t size,
              int (*compar)(const void *, const void *));

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

void *bsearch_s(const void *key, const void *base,
                rsize_t nmemb, rsize_t size,
                int (*compar)(const void *k, const void *y, void *context),
                void *context);
errno_t qsort_s(void *base, rsize_t nmemb, rsize_t size,
                int (*compar)(const void *x, const void *y, void *context),
                void *context);
chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

size refers to the size of one object while num refers to the amount of such objects. I don't see how you can interchange that. For e.g. This,

fwrite(p, 1, 8, s); // Will return 8 if successful.

writes eight 1 Byte elements from p to p + 8 to the file stream s.

Whereas, this,

fwrite(p, 8, 1, s); // Will return 1 if successful.

writes a single 8 Bytes element from p to p + 8 to the file stream s.

The difference is visible when we are dealing with struct objects which may have different data inside them.

The difference is also clearly visible when we are writing to network streams.

It doesn't matter in the case of calloc where the memory cells are contiguous.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Suraj Upadhyay
  • 475
  • 3
  • 9
  • 2
    I don't see how that difference is "clearly visible". The stream receives exactly the same amount of data in exactly the same order. – Paul Ogilvie Feb 13 '21 at 14:52
  • @PaulOgilvie: The author does not mean the difference is clearly visible in the streams. They mean the difference is clearly visible to us, who are considering the semantics. The difference is literally that `fwrite(p, 1, 8, s) != fwrite(p, 8, 1, s)`, since they have different return values. – Eric Postpischil Feb 13 '21 at 15:01