1

I have some array,

float *large_float_array;

that I calloc and attempt to memset after a loop

large_float_array = (float*)calloc(NUM, sizeof(float));

// .. do stuff with float array

memset(large_float_array, 0, sizeof(large_float_array);

// .. do other stuff

but it seems large_float_array is not actually zero'd out. Why is this? Is this due to the special properties of floating point numbers? If so, I'm wondering how I could fix this.

P.S. does calloc actually work here too?

Syntactic Fructose
  • 18,936
  • 23
  • 91
  • 177
  • I just found [this](https://mackiemathew.wordpress.com/2015/06/30/dont-use-memset-for-initializing-floats-or-doubles/) , provides some insight into my question. – Syntactic Fructose May 31 '16 at 21:12
  • `memset(large_float_array, 0, sizeof(large_float_array);` does not compile. Always kind to post true code. – chux - Reinstate Monica May 31 '16 at 21:12
  • 2
    C does not mandate that floating-point be ieee754 specifically. As such I believe there is no guarantee in the standard that `0.0f` be bitwise-zero. – EOF May 31 '16 at 21:12
  • 1
    `large_float_array = (float*)calloc(NUM, sizeof(float));` --> `large_float_array = calloc(NUM, sizeof *large_float_array);` – chux - Reinstate Monica May 31 '16 at 21:14
  • So is the consensus that memset on floating point arrays is ok, as long as I specify the right size? EDIT: @chux why should that be changed? – Syntactic Fructose May 31 '16 at 21:15
  • @Syntactic Fructose Using `memset(ptr, 0, ...` after `ptr = calloc(...)` is redundant. – chux - Reinstate Monica May 31 '16 at 21:16
  • @chux see my comment, I said ``do stuff with large array``, so pretend I filled it with stuff and now need to memset it – Syntactic Fructose May 31 '16 at 21:17
  • As [@AlexD](http://stackoverflow.com/questions/37555614/memset-on-float-array-does-not-fully-zero-out-array#comment62600079_37555614) comment, your code is using the size of a pointer and not the size of a `float`. Using `arge_float_array = calloc(NUM, sizeof *large_float_array);` avoids that problem regardless what type `large_float_array` points to. – chux - Reinstate Monica May 31 '16 at 21:18
  • There are 3 issues here: valid code, correct allocation and the question if `memset(some_float_ptr, 0,,...)` is as good as setting all `float` array elements to `0.0f`. Until problem #1,#2 is fixed. the 3rd issue is moot. – chux - Reinstate Monica May 31 '16 at 21:21
  • If code wants to quickly zero fill a `float` array, use a loop. If that is not fast enough, code could use a helper function that repeatedly calls `memcpy()` to fill the large array with any `float` constant. – chux - Reinstate Monica May 31 '16 at 21:24
  • `memset(large_float_array, 0, sizeof(large_float_array);` (compiler error and wrong size) --> `memset(large_float_array, 0, NUM * sizeof *large_float_array);` Still better to use a loop. – chux - Reinstate Monica May 31 '16 at 21:29

4 Answers4

12

Since large_float_array is a pointer, you cannot use sizeof(large_float_array) to specify the number of bytes to clear: sizeof(large_float_array) is just the size of the pointer, typically 4 or 8 bytes. To clear the whole array, you must write:

memset(large_float_array, 0, NUM * sizeof(float));

or

memset(large_float_array, 0, NUM * sizeof(*large_float_array));

Note that memset sets the memory to all bits zero. The C standard does not guarantee that all bits zero be a representation of 0.0, But in all current architectures it is the case, so it will work as expected.

The strictly conforming way to clear your array is this:

#include <stdlib.h>

...
for (size_t i = 0; i < NUM; i++) {
    large_float_array[i] = 0;
}

If the target systems represents 0.0 as all bits zero, modern compilers will optimize the above loop as a call to memset as can be verified with Goldbolt's Compiler Explorer.

PS: under the same assumption, calloc() would be a better choice: the block returned is initialized to all bits zero and for large sparse matrices, allocating via calloc() may actually be more cache efficient on many systems.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • For those interested in the legality aspect of memset'ing a float, [here's](https://stackoverflow.com/questions/4629853/is-it-legal-to-use-memset-0-on-an-array-of-doubles) the link for that. – rustyx Mar 28 '21 at 10:56
6

You can't use memset because it accepts int as value type, but float may be stored in different way, so you should use a regular cycle

for (size_t i = 0; i < NUM; ++i) {
    large_float_array[i] = 0.;
}
GKFX
  • 1,386
  • 1
  • 11
  • 30
LibertyPaul
  • 1,139
  • 8
  • 26
  • 1
    You should use `size_t` instead of `int` for `i`, as `NUM` could be larger that `int`. – chqrlie May 31 '16 at 21:33
  • @chqrlie does C have the `size_t` type? – LibertyPaul May 31 '16 at 21:34
  • Yes it does. It is defined in `` (among other places). – chqrlie May 31 '16 at 21:36
  • Much better answer. – chux - Reinstate Monica May 31 '16 at 22:01
  • And to avoid some bugs caused by unsigned types, you may want to change to the non-C-Standard but POSIX-Standard `ssize_t` which is a signed `size_t` and is defined in ``. – alx - recommends codidact Mar 18 '19 at 16:55
  • @LibertyPaul: *You can't use `memset` because it accepts `int` as value type*. `memset` does take an `int` argument, but it only uses the value converted as an `unsigned char`. Using `memset` to set an array of `int` would pose a similar problem as for an array of `float`: it would be fine to set the elements to `0`, and some other very specific values, but inappropriate for the general case. – chqrlie Apr 22 '20 at 12:32
  • You can do it (using correct sizes - see top answer) - and it will work at least on all x86/x64/ARM/SPARC/Power CPUs (and actually, on all CPUs which implement IEEE754). While in theory there can be a CPU which doesn't do it (or a compiler which doesn't use CPU and simulates floats itself) - I never ever heard of such an abomination. – No-Bugs Hare Jul 16 '22 at 10:00
3

The effect of intializing memory area by calloc and using memset function with 0 value is actually the same. In other words, calloc is no more "type-wise" than memset. They both just set memory with all bits zero*.

C11 (N1570) 7.22.3.2/2 The calloc function:

The calloc function allocates space for an array of nmemb objects, each of whose size is size. The space is initialized to all bits zero.296)

Footnote 296 (informative only):

Note that this need not be the same as the representation of floating-point zero or a null pointer constant.


*) It's conceivable that calloc could return address of memory location that is already pre-initialized with zeros, thus it may be faster than malloc + memset combo.

Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
1

Post has multiple issues.

Following does not have balanced (). Unclear what OP's true code may be.

memset(large_float_array, 0, sizeof(large_float_array);

Assuming the above simple used another ), then code was only zero filling a few bytes with zero as sizeof(large_float_array) is the size of a pointer.

memset(large_float_array, 0, sizeof(large_float_array));

OP likely wanted something like which would zero fill the allocated space with zero bits. This will bring the allocated memory into the same state as calloc().

memset(large_float_array, 0, NUM *sizeof *large_float_array);

Code should use the following. A good compiler will optimize this trivial loop into fast code.

for (size_t i=0; i<NUM; i++) {
  large_float_array[i] = 0.0f;
}

A way to use the fast memcpy() is to copy the float into the first element of the array, then the first element to the 2nd element, then the first 2 elements to elements 3 & 4, then the first 4 elements to elements 4,5,6,7 ...

void *memset_size(void *dest, size_t n, const char *src, size_t element) {
  if (n > 0 && element > 0) {
    memcpy(dest, src, element);  // Copy first element
    n *= element;
    while (n > element) {
      size_t remaining = n - element;
      size_t n_this_time = remaining > element ? element : remaining;
      memcpy((char*)dest + element, dest, n_this_time);
      element += n_this_time;
    }
  }
  return dest;
}

float pi = M_PI;
float a[10000];
memset_size(a, 10000, &pi, sizeof pi);

As with all optimizations, consider clearly written code first and then employ candidates such as the above in the select cases that warrant it.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256