Arrays of double
can be zero-initialized with the { 0 }
initializer, which works even on systems that have a binary representation of 0.0
that is not all bits zero. While the IEEE 754 representation of floating point zero is all bits zero, the C Standard makes no guarantees about the representation of floating point numbers.
Large dynamically allocated arrays of integer types can be zero-initialized by using calloc()
, but this method is not portable for floating point types, since calloc()
initializes all bits to zero. Assigning a value of 0.0
to each individual double
in the segment using a loop seems grossly inefficient.
I wrote the code for the function calloc_d()
to solve this problem. The function takes the size_t
argument nmemb
, and returns a zero-initialized segment of memory large enough to hold nmemb
double
s. If there is an allocation error, the function returns NULL
. The caller is of course responsible for deallocation.
After using malloc()
to allocate the required amount of memory, the first bytes are assigned the value of 0.0
. Then memmove()
is used to copy the bytes of the first zero, then the bytes of the first two zeros, and so on until at least half of the memory has been initialized. Finally, the remaining memory is initialized to 0.0
by copying bytes from the first half.
The code works, and has no obvious issues, as far as I can tell. I am interested in any comments about shortcomings of this approach, or shortcomings of my code, and suggestions for improvement. I would also be interested in suggestions for alternative, possibly more efficient methods.
I have included the function in a working program below. MAXCOUNT
double
s are allocated for, initialized, and displayed. The value of MAXCOUNT
is currently set to 100
, but I have tested it for values up to 1000000
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXCOUNT 100 // number of doubles to allocate for
double * calloc_d(size_t nmemb);
int main(void)
{
double *dbl_store = calloc_d(MAXCOUNT);
size_t i;
for(i = 0; i < MAXCOUNT; i++) {
if (i % 10 == 0) putchar('\n');
printf("%-8.4f", dbl_store[i]);
}
putchar('\n');
free(dbl_store);
return 0;
}
double * calloc_d(size_t nmemb)
{
double *ret;
double *next; // pointer to beginning of uninitialized segment
size_t alloc_bytes = sizeof(*ret) * nmemb;
size_t init_sz; // size of initialized segment
ret = malloc(alloc_bytes);
next = ret;
if (ret) {
ret[0] = 0.0;
init_sz = sizeof(*ret);
++next;
while (init_sz < (alloc_bytes + sizeof(*ret)) / 2) {
memmove(next, ret, init_sz);
init_sz *= 2;
next = ret + init_sz / sizeof(*ret);
}
memmove(next, ret, alloc_bytes - init_sz);
}
return ret;
}
rep stosq
(or its equivalent) on most platforms. "Repeat store string <size>" is a highly optimised in the hardware, and I doubt it can be beaten by anything else for this purpose. It will also never be slower thanrep movsq
(which I believe is usually how memcpy is implemented) - in factstosq
is almost always faster thanmovsq
because it eliminates the need to read memory while writing to it. \$\endgroup\$ – Gavin Lock Dec 19 '16 at 11:06memmove()
when you can be confident thatmemcpy()
will suffice. That is indeed the case here. \$\endgroup\$ – PellMel Dec 19 '16 at 14:47-O3
optimizations (on gcc). I learned something, which is why I come here :) \$\endgroup\$ – ex nihilo Dec 19 '16 at 14:47memcpy()
can't be used to copy overlapping arrays, and I was interpreting that to mean that it could not be used to copy from an allocated region of memory to the same region. But I now see that this was wrong, and that specificallymemcpy()
can't copy overlapping objects, which is not the case here. Thanks for pointing this out. \$\endgroup\$ – ex nihilo Dec 19 '16 at 14:57