In my September column, I discussed the distinction between allocating objects and allocating storage.1 A C or C++ expression such as:
pt = (T *)malloc(sizeof(T));
allocates storage that's big enough and suitably aligned to hold an object of type T. However, it leaves the allocated storage uninitialized, so it doesn't actually create a T object in that storage. In contrast, a C++ expression of the form:
pt = new T ();
creates a bona fide T object with a coherent initial value.
The distinction between objects and raw storage carries over when allocating array objects. C++ has native facilities to support this distinction. With a little extra effort, C programmers can preserve the distinction as well.
Allocating array storage in C
Calls to malloc commonly use a sizeof expression to specify the size in bytes of the requested storage. To allocate storage for an array, just multiply the size of each array element by the array dimension. For example:
pw = malloc(10 * sizeof(widget));
assigns pw the address of the first widget in storage allocated for an array of 10 widgets.
The Standard C library provides calloc as an alternative way to allocate arrays. Calling calloc(n, s) allocates storage for an array of n objects, each of which is s bytes in size. As with malloc, a call to calloc returns a pointer to the allocated storage if the allocation succeeds, and returns a null pointer otherwise.
For example, to allocate an array of 10 widgets, you can call:
pw = calloc(10, sizeof(widget));
instead of calling:
pw = malloc(10 * sizeof(widget));
The difference between these expressions is in the resulting value of the allocated storage. As mentioned earlier, calling malloc leaves the allocated storage uninitialized. Calling calloc sets all the bits in the allocated storage to zero.
In my previous column on allocating objects,1 I explained that a C++ new-expression can have an empty initializer list, as in:
p = new T ();
When T is a class type, this new-expression invokes T's default constructor (the constructor that can be called without any arguments). When T is a non-class type, such as an integer or pointer type, the new-expression initializes the object with zero (just as if the object were statically allocated).
You can use calloc to initialize objects somewhat akin to the way a C++ new-expression does, but only to a very limited extent. For example, an expression such as:
pi = calloc(1, sizeof(int));
allocates storage for a single int object and initializes that object to zero. The net effect is essentially the same as the effect of the new-expression:
pi = new int ();
For objects of integer types (including boolean and character types), setting all bits to zero in the storage for an object has the same effect as assigning zero to that object. However, while this is almost always the case for pointer and floating-point types, the standards for C and C++ make no such guarantee.2,3 In fact, the C Standard specifically cautions against assuming that the representations of null pointer constants and floating-point zeros have all bits zero.
Thus, given this C code:
double d0 = 0.0; double d1; memset(&d1, 0, sizeof(d1));
the comparison d0 == d1 might not yield a true result. Similarly, given this snippet of C++:
double *p0, *p1; p0 = new double (); p1 = (double *)calloc(1, sizeof(double));
the comparison *d0 == *d1 might not yield true. In C, you shouldn't rely on calloc to properly initialize dynamically allocated objects for anything other than integer types. In C++, you probably shouldn't use calloc at all because new-expressions do the job much better.