42

Is it legal to zero the memory of an array of doubles (using memset(…, 0, …)) or struct containing doubles?

The question implies two different things:

  1. From the point of view of C standard: Is this undefined behavior of not? (On any particular platform, I presume, this cannot be undefined behavior, as it just depends on the in-memory representation of floating-point numbers—that’s all.)

  2. From practical point of view: Is it OK on Intel platform? (Regardless of what the standard is saying.)

Mehdi Charife
  • 722
  • 1
  • 7
  • 22
Andrei
  • 8,606
  • 10
  • 35
  • 43

7 Answers7

35

The C99 standard Annex F says:

This annex specifies C language support for the IEC 60559 floating-point standard. The IEC 60559 floating-point standard is specifically Binary floating-point arithmetic for microprocessor systems, second edition (IEC 60559:1989), previously designated IEC 559:1989 and as IEEE Standard for Binary Floating-Point Arithmetic (ANSI/IEEE 754−1985). IEEE Standard for Radix-Independent Floating-Point Arithmetic (ANSI/IEEE 854−1987) generalizes the binary standard to remove dependencies on radix and word length. IEC 60559 generally refers to the floating-point standard, as in IEC 60559 operation, IEC 60559 format, etc. An implementation that defines __STDC_IEC_559__ shall conform to the specifications in this annex. Where a binding between the C language and IEC 60559 is indicated, the IEC 60559-specified behavior is adopted by reference, unless stated otherwise.

And, immediately after:

The C floating types match the IEC 60559 formats as follows:

  • The float type matches the IEC 60559 single format.
  • The double type matches the IEC 60559 double format.

Thus, since IEC 60559 is basically IEEE 754-1985, and since this specifies that 8 zero bytes mean 0.0 (as @David Heffernan said), it means that if you find __STDC_IEC_559__ defined, you can safely do a 0.0 initialization with memset.

ib.
  • 27,830
  • 11
  • 80
  • 100
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • So its IEEE754 unless the compiler documentation explicitly states otherwise? – Martin York Jan 07 '11 at 23:27
  • 8
    Actually, as I understood it, it is the contrary: it may be anything, but if you find defined `__STDC_IEC_559__` you can be sure that it's IEC 60559 aka IEEE 754. – Matteo Italia Jan 07 '11 at 23:29
15

If you are talking about IEEE754 then the standard defines +0.0 to double precision as 8 zero bytes. If you know that you are backed by IEEE754 floating point then this is well-defined.

As for Intel, I can't think of a compiler that doesn't use IEEE754 on Intel x86/x64.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    What about all the mobile devices that people are developing for. Do they all use IEEE754? – Martin York Jan 07 '11 at 23:27
  • Still, from a practical standpoint I don't think that anyone designing a floating-point format will ever make it such that setting a `double` to all zero will make it become problematic. `:)` – Matteo Italia Jan 07 '11 at 23:47
7

David Heffernan has given a good answer for part (2) of your question. For part (1):

The C99 standard makes no guarantees about the representation of floating-point values in the general case. §6.2.6.1 says:

The representations of all types are unspecified except as stated in this subclause.

...and that subclause makes no further mention of floating point.

You said:

(on a fixed platform, how can this UB ... it just depends of floating representation that's all ...)

Indeed - there a difference between "undefined behaviour", "unspecified behaviour" and "implementation-defined behaviour":

  • "undefined behaviour" means that anything could happen (including a runtime crash);
  • "unspecified behaviour" means that the compiler is free to implement something sensible in any way it likes, but there is no requirement for the implementation choice to be documented;
  • "implementation-defined behaviour" means that the compiler is free to implement something sensible in any way it likes, and is supposed to document that choice (for example, see here for the implementation choices documented by the most recent release of GCC);

and so, as floating point representation is unspecified behaviour, it can vary in an undocumented manner from platform to platform (where "platform" here means "the combination of hardware and compiler" rather than just "hardware").

(I'm not sure how useful the guarantee that a double is represented such that all-bits-zero is +0.0 if __STDC_IEC_559__ is defined, as described in Matteo Italia's answer, actually is in practice. For example, GCC never defines this, even though is uses IEEE 754 / IEC 60559 on many hardware platforms.)

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Matthew Slattery
  • 45,290
  • 8
  • 103
  • 119
5

Even though it is unlikely that you encounter a machine where this has problems, you may also avoid this relatively easily if you are really talking of arrays as you indicate in the question title, and if these arrays are of known length at compile time (that is not VLA), then just initializing them is probably even more convenient:

double A[133] = { 0 };

should always work. If you'd have to zero such an array again, later, and your compiler is compliant to modern C (C99) you can do this with a compound literal

memcpy(A, (double const[133]){ 0 }, 133*sizeof(double));

on any modern compiler this should be as efficient as memset, but has the advantage of not relying on a particular encoding of double.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 2
    Are you sure it compiles to the same? I would expect the latter to allocate 133*8 bytes on the stack, `memset` them to 0 (either inline or with a call), then call `memcpy` in a naive implementation. – R.. GitHub STOP HELPING ICE Jan 07 '11 at 23:04
  • 1
    @R..: The same, probably not. And with the 133 I was perhaps exaggerating a bit. But for more reasonable values gcc and clang are able to transform this into just storing zeros. opencc and icc generate an extra copy, and there opencc seems to be the worst in generating a call to `memcpy`, indeed. I said *should* not *will* :) – Jens Gustedt Jan 08 '11 at 09:10
1

As Matteo Italia says, that’s legal according to the standard, but I wouldn’t use it. Something like

double *p = V, *last = V + N;  // N is count
while (p != last) *(p++) = 0;

is at least twice faster.

ib.
  • 27,830
  • 11
  • 80
  • 100
Andrei Sfrent
  • 189
  • 1
  • 11
  • 10
    I seriously doubt it's faster, and would expect it to be much slower. `memset` is typically the most-optimized function in any C library implementation, for good reason. – R.. GitHub STOP HELPING ICE Jan 07 '11 at 23:03
  • 1
    It may not be faster, but it is safer and the speed difference will be negligible in most situations. – Martin York Jan 07 '11 at 23:30
  • I tested the code before posting it (for N = 10 ^ 7, on a Linux machine) and seemed to be faster than memset(V, 0, N * sizeof(double)). – Andrei Sfrent Jan 07 '11 at 23:44
  • A memset is often inlined directly by the compiler and also implemented in the VPU, which mean it can clear 128 bits in each iteration. If your loop is faster you probably use a crappy compiler or have these optimizations switched off. – onemasse Jan 08 '11 at 08:13
  • Tested on several machines running Ubuntu / Debian and it seems memset is faster if the area contains a lot of zeros already. I compiled my code with "gcc -o memsettest memsettest.c -O2". – Andrei Sfrent Jan 08 '11 at 12:30
  • 1
    This is wrong. Without optimizations, the `memset` will be faster. With optimizations, any modern compiler should generate the same code. – Acorn May 08 '19 at 09:42
0

It’s “legal” to use memset. The issue is whether it produces a bit pattern where array[x] == 0.0 is true. While the basic C standard doesn’t require that to be true, I’d be interested in hearing examples where it isn’t!

It appears that setting to zero via memset is equivalent to assigning 0.0 on IBM-AIX, HP-UX (PARISC), HP-UX (IA-64), Linux (IA-64, I think).

Here is a trivial test code:

double dFloat1 = 0.0;
double dFloat2 = 111111.1111111;

memset(&dFloat2, 0, sizeof(dFloat2));

if (dFloat1 == dFloat2) {
    fprintf(stdout, "memset appears to be equivalent to = 0.0\n");
} else {
    fprintf(stdout, "memset is NOT equivalent to = 0.0\n");
}
ib.
  • 27,830
  • 11
  • 80
  • 100
davep
  • 286
  • 1
  • 4
  • 1
    The test is flawed: it does not distinguish `0.0` and `-0.0` which are different. A stronger proof is `if (!memcmp(&dFloat1, &dFloat2, sizeof dFloat1)) ...` – chqrlie Jul 16 '22 at 10:23
-1

Well, I think the zeroing is "legal" (after all, it's zeroing a regular buffer), but I have no idea if the standard lets you assume anything about the resulting logical value. My guess would be that the C standard leaves it as undefined.

user541686
  • 205,094
  • 128
  • 528
  • 886