Using memset() on struct which contains a floating point number
Asked Answered
Y

4

5

In a C/C++ hybrid project, I found some code that I could reduce to

#include <mem.h>

struct StructContainingDouble
{
    double d;
    /// other elements omitted
};

void clear(StructContainingDouble* p)
{
    memset(p, 0, sizeof *p);
}

without stopping Cppcheck to raise the portability warning

Using memset() on struct which contains a floating point number.

The message is correct, but since the floating-point number is declared double, it seems to be false positive, because in double the (positive) zero value is encoded following the IEEE 754 standard:[*]

0 00000000000 0000000000000000000000000000000000000000000000000000

So I'd tend to simply suppress the warning and forget about it

void clear(ContainingDouble* p)
{
    // cppcheck-suppress memsetClassFloat
    memset(p, 0, sizeof *p);
}

But maybe there is really a portability issue here?

Addendum:

The actual code is based on the Win32 platform. The structure is used for managing access to shared memory, that's why constructors are useless. And it's not only one object of that structure that has to be zeroed, but an array of it that is embedded in another structure, something like this:

#include <mem.h>

struct Slot
{
    double d;
    // more members...
};

struct SharedMem
{
    Slot slots[2048];
    // more members...
};

void clear(SharedMem* p)
{
    memset(p, 0, sizeof *p);
}

[*] from: Double-precision floating-point format - Wikipedia

Yttrium answered 3/8, 2017 at 10:23 Comment(14)
The C language does not guarantee IEEE 754 floating point.Kyne
@Kyne interesting, but C++ does? Is there some C standard that includes it? In my case it's a C++ project with some old(-style) C code in it.Yttrium
C++ does not guarantee IEEE 754 either.Dicot
1) There is only one C and only one C++ stantard. 2) There is no "C(C++" language. They are different languages, you cannot assume the same behaviour for identical syntax/names. There is no portable way to zero an object with other than integer types in C. Use a "zero-structure" and memcpy.Brockie
Mixing C++ with C isn't really that rare. If I had a zero-structure I'd prefer assignment: T val = T::zero or *val_ptr = T::zero.Yttrium
Since no one else has said it. Why? If you've got a big array and don't want to loop through it there might be a performance hit. But if this is a single structure aren't you worrying about a tiny overhead?Connivent
It might because people might suggest alternatives based on what you're actually doing. You didn't list the other members. Did you know the bit-pattern for nullptr doesn't have to zeroes either? But again I can't believe could be the case for a Windows machine.Zeroing can only (fully portably) initialize integer types.Connivent
@Connivent It's actually a structure holding an array of structure, I added the details to the question. For better answers it makes sense to add the details.Yttrium
So why not memset(p,0,2048*sizeof *p) and be done with it?Connivent
@Connivent SharedMem has more members as well. And the warning would not go either.Yttrium
Oh, I see. You're not initializing slots but the whole shootin' match.Connivent
Are you aware std::calloc() just returns zeroed memory? Has to be released by std::free() and the C++ crowd will have kittens...Connivent
@Connivent std::calloc() is no option here. I already added that it's shared memory (it has to).Yttrium
It would be interesting to know what a fully compliant clear() performance (time, code space, memory space) would be compared to this memset() version. I suspect the differences would be moot in the larger coding picture. IMO 6.1 vs half-dozen of the other situation.Buford
K
3

It was already mentioned that C does not guarantee any particular floating point implementation.

Well, neither does C++. No specific implementation of floating point values is required.

[basic.fundamental]

There are three floating point types: float, double, and long double. [...] The value representation of floating-point types is implementation-defined.

So, memset-ing a floating point value is not portable, in either C or C++.

Kyat answered 3/8, 2017 at 10:53 Comment(5)
i think that it depends on the target hardware platform support for fp numbers.Tangy
@Tangy No. Some platforms where hardware does not support it, implement IEEE 754 arithmetic in software.Dicot
@Tangy this is exactly what portability is aboutYttrium
Seems I have to accept this fact. Is it at least for "desktop applications" reasonable to assume that it will be okay to use memset? In my actual case it initializes shared memory under Windows (wich is much less portable) - so I'll probable better keep the code as it is.Yttrium
x86 natively supports ieee754, so it should be safe to assume that this is portable across x86 platforms. Not idea about the others.Tangy
C
5

Is it a problem? Yes. No, not really. A very remote maybe.

As pointed out in the comments by interjay C does not mandate format of floating-point values. So in theory 'zeroes' could be an invalid value or not represent zero.

All modern platforms implement IEEE 754 if they have an FPU. Any (likely embedded) system that doesn't will implement IEEE 754 in its library (unless it's a degenerate implementation and simply doesn't have floating-point...).

So while there is no guarantee it's very unlikely you'll encounter a problem here. There are issues on some platforms regarding fine detail of how precisely they conform to the letter for IEEE 754 but having asked before and never got any answer I'll be excited to here of a platform where memset() a double with zeroes isn't setting it to the value of zero.

That said, don't forget endianness if you start chopping double up but with zero that's not an issue.

Remember C is getting near to 50 years old and things that are now almost universal such as a byte is 8 bits hadn't settled down and I think quite rightly in order to offer maximum support (don't forget embedded) it's never committed to these standards.

In paranoia mode you could go with:

memset(&obj,0,sizeof obj);
#ifndef __STDC_IEC_559__
    obj.dbl_val=0.0;
#endif

Where dbl_val is the double member of obj. However the only likely reason for that to be undefined isn't because block zeroing isn't zero but some other fine point of the standard isn't fully implemented.

Connivent answered 3/8, 2017 at 11:14 Comment(2)
Interesting: I was thinking of a compile-time check for IEEE support. And the actual unit is much less portable, because coupled on Win32 shared memory handling.Yttrium
A Win32 platform that isn't IEEE? I don't know that it's defined anywhere but that would be a very very rare beast.Connivent
W
5

I think that the portability considerations have gone too far in this particular case. I have not seen in my life (I do programming for 30+years) any hardware or C implementation where zero means something else than zero. Of course it is possible to create any number format where zero is not zero (for example BDC with the numbers represented by the ASCII digit codes - but it is not the case here), but we discuss the real existing hardware and software.

So IMO memset to zeroes is safe and portable even if the Standard says that the float point numbers are implementation defined, as I do not know any existing implementations where zero means something else than zero, and such implementation would extremely impractical.

Wheelwright answered 3/8, 2017 at 11:28 Comment(2)
As I checked out, using ZeroMemory would be the better choice. But sadly also this triggers the same portability issue in Cppcheck. (memsetting without the zero value would be problematic)Yttrium
Yes it will. And it is correct for the reasons stated in another answers and comments. Setting the memory occupied by the float is definitely an UB, but again zero in the real world is IMO not. But the warning has to be issued.Wheelwright
K
3

It was already mentioned that C does not guarantee any particular floating point implementation.

Well, neither does C++. No specific implementation of floating point values is required.

[basic.fundamental]

There are three floating point types: float, double, and long double. [...] The value representation of floating-point types is implementation-defined.

So, memset-ing a floating point value is not portable, in either C or C++.

Kyat answered 3/8, 2017 at 10:53 Comment(5)
i think that it depends on the target hardware platform support for fp numbers.Tangy
@Tangy No. Some platforms where hardware does not support it, implement IEEE 754 arithmetic in software.Dicot
@Tangy this is exactly what portability is aboutYttrium
Seems I have to accept this fact. Is it at least for "desktop applications" reasonable to assume that it will be okay to use memset? In my actual case it initializes shared memory under Windows (wich is much less portable) - so I'll probable better keep the code as it is.Yttrium
x86 natively supports ieee754, so it should be safe to assume that this is portable across x86 platforms. Not idea about the others.Tangy
Y
1

The actual code is used to initialize shared memory under Windows (Win32). That means that the code will most probably be ported (if at all) to some platform that has appropriate IEEE 754 support.

That's why I suggest to couple the non-portabilities:

  1. Use the ZeroMemory macro defined in WinBase.h (via Windows.h) as to explicitly state that you are not attempting to store some fancy bit patterns into floating point data.

  2. Disable the portability warning of Cppcheck right before that call.

In other words:

#include <windows.h>

// ...

void clear(SharedMem* p)
{
    // cppcheck-suppress memsetClassFloat
    ZeroMemory(p, sizeof *p);
}
Yttrium answered 3/8, 2017 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.