Type punning

S

Skarmander

This:
#include <stdint.h>

union r_float {
float f;
uint32_t i;
};

/* .. */
float f;
uint32_t i;
union r_float ra;

/* .. */
ra.f = f;
i = ra.i;

depends only on implementation-defined aspects, right? That is, if I know
uint32_t exists and the sizes of the objects are identical, will 'i' and 'f'
have identical bit patterns after the assignments? Similarly, will the same
hold for:

ra.i = i;
f = ra.f;

if I know the bit pattern of 'i' is not a trap representation for 'float'?

S.
 
R

Robert Gamble

Skarmander said:
This:
#include <stdint.h>

union r_float {
float f;
uint32_t i;
};

/* .. */
float f;
uint32_t i;
union r_float ra;

/* .. */
ra.f = f;
i = ra.i;

depends only on implementation-defined aspects, right? That is, if I know
uint32_t exists and the sizes of the objects are identical, will 'i' and 'f'
have identical bit patterns after the assignments? Similarly, will the same
hold for:

It is not guaranteed by the Standard that objects of the same size will
overlap bit for bit. If your implementation does guarantee that they
will and you know that the objects are the same size and that the
values specified for one member represent a valid value for the other
member then there is no undefined behavior as far as I can see.
ra.i = i;
f = ra.f;

if I know the bit pattern of 'i' is not a trap representation for 'float'?

Same as above.

Robert Gamble
 
M

Michael Mair

Skarmander said:
This:
#include <stdint.h>

union r_float {
float f;
uint32_t i;
};

/* .. */
float f;
uint32_t i;
union r_float ra;

/* .. */
ra.f = f;
i = ra.i;

depends only on implementation-defined aspects, right? That is, if I
know uint32_t exists and the sizes of the objects are identical, will
'i' and 'f' have identical bit patterns after the assignments?
Similarly, will the same hold for:

ra.i = i;
f = ra.f;

if I know the bit pattern of 'i' is not a trap representation for 'float'?

As long as there are no padding bits whatsoever or if the padding bits
are the same.

Cheers
Michael
 
S

Skarmander

Michael said:
As long as there are no padding bits whatsoever or if the padding bits
are the same.
uint32_t has no padding bits, by definition; for a 'float' "padding bits"
are not defined, since the entire representation is unspecified, but this
should be covered by "no trap representations".

S.
 
M

Michael Mair

Skarmander said:
uint32_t has no padding bits, by definition;

You are right, of course.
for a 'float' "padding
bits" are not defined, since the entire representation is unspecified,
but this should be covered by "no trap representations".

Padding bits may be harmless, trap representations are not, cf
C99, 6.2.6.2. In addition, trap representations may not depend on
padding bits at all.
As one does not need 32 bits to meet the requirements for float,
padding bits are possible. There is AFAICS no rule in the standard
that padding bits have to stay the same all the time, so the
implementation may change the representation to help SETI@home
with spare bits for all I know.

Cheers
Michael
 
S

Skarmander

Michael Mair wrote:
Padding bits may be harmless, trap representations are not, cf
C99, 6.2.6.2. In addition, trap representations may not depend on
padding bits at all.

Yes. The intent is that the representation of 'float's is known, at least
well enough to avoid trap representations.
As one does not need 32 bits to meet the requirements for float,
padding bits are possible. There is AFAICS no rule in the standard
that padding bits have to stay the same all the time,

That is true, but it would take a DeathStation 9000 to have floats where
xpadding bits can change at whim *and* cause trap representations if a
previously observed pattern is stored. That's not to say such hardware
cannot exist, but I'll be content mentioning in a footnote that you are
kindly not to compile the program on such platforms.
so the implementation may change the representation to help SETI@home
with spare bits for all I know.
That would be a fascinating application and I for one would love to see it,
but I think we can discount that possibility.

S.
 
J

Jordan Abel

Michael Mair wrote:


Yes. The intent is that the representation of 'float's is known, at least
well enough to avoid trap representations.


That is true, but it would take a DeathStation 9000 to have floats where
xpadding bits can change at whim *and* cause trap representations if a
previously observed pattern is stored. That's not to say such hardware
cannot exist, but I'll be content mentioning in a footnote that you are
kindly not to compile the program on such platforms.

That would be a fascinating application and I for one would love to see it,
but I think we can discount that possibility.

S.

What about memcpy in that case? to an unsigned char array and back - i
think that if that could cause a later read as a float to become a trap
representation it _would_ render the implementation non-conforming [so,
no DS9K]
 
S

Skarmander

Jordan said:
Michael Mair wrote:

Yes. The intent is that the representation of 'float's is known, at least
well enough to avoid trap representations.

That is true, but it would take a DeathStation 9000 to have floats where
xpadding bits can change at whim *and* cause trap representations if a
previously observed pattern is stored. That's not to say such hardware
cannot exist, but I'll be content mentioning in a footnote that you are
kindly not to compile the program on such platforms.

That would be a fascinating application and I for one would love to see it,
but I think we can discount that possibility.

What about memcpy in that case? to an unsigned char array and back - i
think that if that could cause a later read as a float to become a trap
representation it _would_ render the implementation non-conforming [so,
no DS9K]

Hm. I've just tested it on gcc and it is actually capable of turning this
into a no-op, so a memcpy() would be a maximally portable solution that at
least some compilers could handle perfectly.

On the other hand, if the platform does *not* optimize away these memcpy()
calls, you take a significant performance hit for little appreciable gain;
that is, the ability to use this code unmodified even if float uses demonic
padding bits. (Not just any padding bits, mind you. Demonic ones.) And then
you still have to see whether platforms with such problems actually get the
memcpy() right, too -- at this microlevel, it's no good to be able to thumb
your nose at a non-conforming platform if your code still won't work.

The best solution is probably still to use a simple reinterpreting through a
union, with a clear explanation of when this can and cannot be expected to
work, and offering the more portable but possibly unacceptably slow
solutions as an alternative.

S.
 
J

Jordan Abel

On the other hand, if the platform does *not* optimize away these memcpy()
calls, you take a significant performance hit for little appreciable gain;
that is, the ability to use this code unmodified even if float uses demonic
padding bits. (Not just any padding bits, mind you. Demonic ones.)

My point is that demonic padding bits are illegal according to the
standard for this reason.
 
S

Skarmander

Jordan said:
My point is that demonic padding bits are illegal according to the
standard for this reason.

I don't see how that follows. An implementation could work around its
demonic padding bits in calls to memcpy() (or in general if objects with
them are aliased through char types), and as you point out, the standard
would in fact require it to. But if I access a float in a union through a
member of type uint32_t, I am not similarly protected, and the demonic
padding bits could take wing and strafe me.

There might be a longer chain of reasoning with definitions in the standard
that actually prohibit DPBs, but I don't think the direct approach you used
works.

S.
 
S

Skarmander

Skarmander said:
I don't see how that follows. An implementation could work around its
demonic padding bits in calls to memcpy() (or in general if objects with
them are aliased through char types), and as you point out, the standard
would in fact require it to. But if I access a float in a union through
a member of type uint32_t, I am not similarly protected, and the demonic
padding bits could take wing and strafe me.

There might be a longer chain of reasoning with definitions in the
standard that actually prohibit DPBs, but I don't think the direct
approach you used works.

Here's a mostly but not completely irrelevant side note: I have actually
worked with a platform that had a special-purpose area of RAM for which only
the low nybbles of each byte contained anything meaningful. The high nybbles
contained random values on a read and ignored writes. These were not DPBs,
of course, since writing them was perfectly benign, and a regular
implementation of memcpy() would simply work.

S.
 
S

S.Tobias

Robert Gamble said:
It is not guaranteed by the Standard that objects of the same size will
overlap bit for bit. If your implementation does guarantee that they
will and you know that the objects are the same size and that the
values specified for one member represent a valid value for the other
member then there is no undefined behavior as far as I can see.
If it were defined, then unions would defeat the purpose of aliasing
rules:

uint32_t f(float *pf, uint32_t *pi)
{
*pi = /*...*/; //Can be reordered? Aliasing says "yes".
*pf = /*...*/;
return *pi;
}

f(&ra.f, &ra.i);

I think conversion of the expression `ra.i' to value should yield UB
(but I may be wrong, so I kindly ask for more explanation).
 
T

Tim Rentsch

Skarmander said:
This:
#include <stdint.h>

union r_float {
float f;
uint32_t i;
};

/* .. */
float f;
uint32_t i;
union r_float ra;

/* .. */
ra.f = f;
i = ra.i;

depends only on implementation-defined aspects, right? That is, if I know
uint32_t exists and the sizes of the objects are identical, will 'i' and 'f'
have identical bit patterns after the assignments?

Certainly that seems to be the intention. See DR283.

Similarly, will the same
hold for:

ra.i = i;
f = ra.f;

if I know the bit pattern of 'i' is not a trap representation for 'float'?

Almost. A float can have multiple representations corresponding to a
single value (eg, normalization). The assignment 'f = ra.f;' might
normalize the bit pattern in ra.f, so in some cases the two bit
patterns might be different.
 
T

Tim Rentsch

Skarmander said:
This:
#include <stdint.h>

union r_float {
float f;
uint32_t i;
};

/* .. */
float f;
uint32_t i;
union r_float ra;

/* .. */
ra.f = f;
i = ra.i;

depends only on implementation-defined aspects, right? That is, if I know
uint32_t exists and the sizes of the objects are identical, will 'i' and 'f'
have identical bit patterns after the assignments?

Except, of course, that normalization changes can also happen
on the assignment 'ra.f = f;'. But the bit patterns in i and
ra.f should be identical.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,226
Members
46,815
Latest member
treekmostly22

Latest Threads

Top