Reputation: 8479
I have a struct like below:
struct pts_t
{
uint32_t lsb;
uint32_t msb;
};
I would like cast that into a double. Is it safe of directly write:
pts_t t;
double timestamp = t;
and more complex, if the struct type is part of a C dll API, without having "packing" attribute (for all compiler) in that case I have to copy the pts_t*
receive througth the API to pts_t instance i create to control the struct packing ?
void f(pts_t* t)
{
pts_t myt; myt.lsb = t->lsb; myt.msb = t->msb;
double timestamp = *(double*)(&myt.lsb);
}
Upvotes: 6
Views: 1629
Reputation: 3761
The initial thought would be to write the following:
double timestamp = *( ( double * ) &( t.lsb ) );
To step through this (assuming you are in a 32-bit environment):
t.lsb
because you need to find the memory address of the first byte in your structure. Note, you can alternatively do &t
.timestamp
uses.Remark:
Now, the three points in the remark blurb are a lot to worry about. It becomes a big pain when porting this code accross multiple platforms. As mentioned below, using C unions
is a much better and correct solution that is portable.
It would be written as follows with C unions
:
double timestamp = ( union { double d; struct pts_t pts; } ) { t } .d;
Upvotes: 3
Reputation: 263507
You have:
struct pts_t
{
uint32_t lsb;
uint32_t msb;
};
This:
pts_t t;
double timestamp = t;
is perfectly "safe", in the sense that it won't compile so it can't do any harm. You haven't defined a type pts_t
; you've defined a type struct pts_t
.
This:
struct pts_t t;
double timestamp = t;
also won't compile, because you can't convert (either explicitly, with a cast, or implicitly, with an assignment) a value of a struct type to an object of numeric type.
I humbly suggest that you would have saved some time if you'd tried that before posting.
Probably the most straightforward approach is to use memcpy()
:
#include <assert.h>
#include <string.h>
/* ... */
struct pts_t t = { some_lsb_value, some_msbZ_value };
double timestamp;
assert(sizeof t == sizeof timestamp);
memcpy(×tamp, &t, sizeof timestamp);
By using memcpy()
rather than pointer casts, you avoid any risk of misaligned memory access; depending on the system, your struct might require either more or less strict alignment than a double
.
There's also no guarantee that your struct is the same size as a double
. The struct is almost certainly 64 bits, and double
is probably 64 bits, but neither is actually guaranteed, and it doesn't hurt to make your assumptions explicit.
That leaves open the question of whether the values you've stored in t.lsb
and t.msb
, when taken together, make up a representation for a valid double
value, particularly for the value you want. The language says very little about how floating-point types are represented. In particular, endianness can and does vary across different systems. It's up to you to make sure that reinterpreting the representation in this way actually makes sense -- and your code is likely to be non-portable.
Upvotes: 0
Reputation: 78953
Even if you assume that your double
are 64 bit wide, if you are looking for portable code you should be really careful with that: your structure and double
might have different alignment constraints and your compiler may get confused because of aliasing rules. A way to avoid problems with that is to use a union
union {
struct pts_t pts;
double timestamp;
} x = { .pts = t };
and then use x.timestamp
.
Also be careful that "composing" double
s like that might result in strange values such as infinities that you wouldn't encounter otherwise.
Upvotes: 4
Reputation: 262
You have to write
double timestamp = *( (double*)(&t.lsb));
You should use something like
struct __attribute__((__packed__)) pts_t {
...
};
to make sure that your struct is packed (though I can't see why any compiler would pad something after the lsb in this case).
...
Actually depending whether your platform is big- or littleendian you might have to switch lsb and msb or do something like this:
double timestamp;
double* p_timestamp = ×tamp;
*((uint32_t*)p_timestamp) = t.msb;
*( ((uint32_t*)p_timestamp) + 1) = t.lsb;
Upvotes: 3