Clarification on type punning.

F

Francis Moreau

Hello,

I did some googling to understand what "type punning" means and I
found, in my understanding, it is no more and no less than a what's
done by a cast operator.

I wrote a simple example, that makes me feel I'm really missing
something, so I'd like to show you that small piece of code:

struct bar {
double a;
};

struct foo {
int a;
};

struct foo global;

void func_ok(void)
{
struct bar *g = (struct bar *)&global;
g->a++;
}

void func_ko(void)
{
((struct bar *)&global)->a++;
}

Here, gcc emits the following warning:

In function ‘func_ko’: warning: dereferencing type-punned pointer will
break strict-aliasing rules.

I can understand the warning, but why doesn't 'func_ok()' emit the
same warning too ?

Thanks
 
T

Tom St Denis

Hello,

I did some googling to understand what "type punning" means and I
found, in my understanding, it is no more and no less than a what's
done by a cast operator.

I wrote a simple example, that makes me feel I'm really missing
something, so I'd like to show you that small piece of code:

struct bar {
        double a;

};

struct foo {
        int a;

};

struct foo global;

void func_ok(void)
{
        struct bar *g = (struct bar *)&global;
        g->a++;

}

void func_ko(void)
{
         ((struct bar *)&global)->a++;

}

Here, gcc emits the following warning:

In function ‘func_ko’: warning: dereferencing type-punned pointer will
break strict-aliasing rules.

I can understand the warning, but why doesn't 'func_ok()' emit the
same warning too ?

It's when you dereference the casted pointed I imagine GCC picks that
up. 'g' is a valid type of pointer to the right structure by time you
modify the value inside the struct. In "ok" you have two statements,
in "ko" you have one statement.

Aside of to/from void * the only real time to do any pointer casting
is to add/remove "const" when you get stuck in API nastyness, e.g. a
function which takes "void *" and you want to pass it "const void *".
The "real" solution is to address the API nastyness, but sometimes a
short term "cast that to non-const" will do.

So if you're casting one non-void pointer to another non-void pointer
you're probably doing something wrong, or at the very least highly non-
portable

Tom
 
B

Ben Bacarisse

Francis Moreau said:
I did some googling to understand what "type punning" means and I
found, in my understanding, it is no more and no less than a what's
done by a cast operator.

I think you've been somewhat misinformed if that is the impression you
are come away with. You do what most people would call type punning
without any cast operators in sight, and there are uses of cast operators that
would, in my view, not constitute type punning (for example
printf("%lu\n", (unsigned long)sizeof(s));).

The key element is, in my option, a direct re-interpretation of the
representation (the in some sense "the bits") of a value of one type
as a value of some other type. Yes, this is often done with pointer
cast and a dereference of some sort, but you can also do it with a
union, and by using implicit pointer conversions.

<snip>
 
M

Mark Bluemel

Hello,

I did some googling to understand what "type punning" means and I
found, in my understanding, it is no more and no less than a what's
done by a cast operator.

I don't think so...

Let's assume for the sake of illustration that an integer and a float,
in some implementation have the same size. Then if we code this:

float f = 3.0;
int i = (int) f;
int j = *((int *)&f);

Would you expect i and j to be equal?

In the case of i, the cast actually converts a value (I'd expect i to
be 3 or perhaps 2).

In the case of j, we have simply interpreted the bit pattern
representing the float value 3.0 as an integer value.
 
F

Francis Moreau

The key element is, in my option, a direct re-interpretation of the
representation (the in some sense "the bits") of a value of one type
as a value of some other type.  Yes, this is often done with pointer
cast and a dereference of some sort,

yes, actually I was thinking about pointer cast only, sorry for not
having pointed out this sooner.

So in case of pointer cast (cf my previous example), can I say that
both func_ok() and func_ko() are doing type punning ?

If so why does gcc emit a warning only for func_ko() ?

thanks
 
F

Francis Moreau

I don't think so...

Let's assume for the sake of illustration that an integer and a float,
in some implementation have the same size. Then if we code this:

   float f = 3.0;
   int i = (int) f;
   int j = *((int *)&f);

Would you expect i and j to be equal?

In the case of i, the cast actually converts a value (I'd expect i to
be 3 or perhaps 2).

In the case of j, we have simply interpreted the bit pattern
representing the float value 3.0 as an integer value.

You're right, but was thinking about pointer casts, sorry for not
being accurate.

thanks
 
B

Ben Bacarisse

Francis Moreau said:
yes, actually I was thinking about pointer cast only, sorry for not
having pointed out this sooner.

So in case of pointer cast (cf my previous example), can I say that
both func_ok() and func_ko() are doing type punning ?

I would say so, yes.
If so why does gcc emit a warning only for func_ko() ?

I didn't answer that before because I don't know! C's type system is
such that you can't always tell if strict aliasing rules have been
broken, so gcc can't warn about every case. Why it gives up so easily
and reports only the direct dereference, I don't know. If you really
want to know, a gcc group might have someone able to say why, but I'd
lay money on it being simply that the "effective type" is not tracked
though the data flow analysis so once the assignment has happened gcc
just takes your word that you know what you are doing.
 
J

jameskuyper

Francis said:
Hello,

I did some googling to understand what "type punning" means and I
found, in my understanding, it is no more and no less than a what's
done by a cast operator.

Type punning is both more AND less that what's done by a cast
operator.

It's more than what's done by a cast operator, because it's not
sufficient to convert a pointer of one type into a pointer of a
different type; it order for type punning to occur, the converted
pointer must then be dereferenced, which necessarily involves at least
one more operator.

Type punning is also less than what's done by the cast operator,
because most uses of the cast operator don't involve pointers at all,
and even the ones that do aren't necessarily part of a type-pun.

Type punning can also be done by other methods, most notably by the
mis-use of unions. It can also be done using implicit type conversions
through void*, with not a single cast operator involved.
 
T

Tom St Denis

yes, actually I was thinking about pointer cast only, sorry for not
having pointed out this sooner.

So in case of pointer cast (cf my previous example), can I say that
both func_ok() and func_ko() are doing type punning ?

If so why does gcc emit a warning only for func_ko() ?

Because you have two statements in ko.

struct bar *g = (struct bar *)&global;

This is "legal" C in that you're casting a pointer of one type to
another. Whether dereferencing it makes any sense is another
question. In this case sizeof struct foo * == sizeof struct bar *, so
in terms of holding an address they appear interchangeable to the
compiler on the platform. No warning required.

g->a++;

At the point where the compiler sees this statement 'g' is initialized
and has a non-const 'a' member that you're allowed to modify. GCC
doesn't keep track of what you initialized 'g' with apparently. But
I suspect that'd be hard to do.

What you really want is to warn when casting from a non-void to a non-
void.

Tom
 
F

Francis Moreau

I would say so, yes.


I didn't answer that before because I don't know!  C's type system is
such that you can't always tell if strict aliasing rules have been
broken, so gcc can't warn about every case.  Why it gives up so easily
and reports only the direct dereference, I don't know.

Maybe because this is how inheritance is usually implemented in C:
casting a generic object into a more specialized object, so generating
a warning in such case would result in ton of confusing warnings in a
lot of places.
If you really
want to know, a gcc group might have someone able to say why, but I'd
lay money on it being simply that the "effective type" is not tracked
though the data flow analysis so once the assignment has happened gcc
just takes your word that you know what you are doing.

Not sure since in the following expression taken from my previsous
example:

struct bar *g = (struct bar *)&global;

it seems trivial to detect type punning since 'struct bar' type is not
compatible with 'struct foo' one.

Thanks
 
F

Francis Moreau

Type punning is both more AND less that what's done by a cast
operator.

It's more than what's done by a cast operator, because it's not
sufficient  to convert a pointer of one type into a pointer of a
different type; it order for type punning to occur, the converted
pointer must then be dereferenced, which necessarily involves at least
one more operator.

ok so that means that type punning happens only with pointer cast.
Type punning is also less than what's done by the cast operator,
because most uses of the cast operator don't involve pointers at all,
and even the ones that do aren't necessarily part of a type-pun.

Really ?

I saw ton of code out there which uses pointer casts was used to
implement inheritance in C.

thanks
 
F

Francis Moreau

Because you have two statements in ko.

        struct bar *g = (struct bar *)&global;

This is "legal" C in that you're casting a pointer of one type to
another.  Whether dereferencing it makes any sense is another
question.  In this case sizeof struct foo * == sizeof struct bar *, so
in terms of holding an address they appear interchangeable to the
compiler on the platform.  No warning required.

        g->a++;

At the point where the compiler sees this statement 'g' is initialized
and has a non-const 'a' member that you're allowed to modify.  GCC
doesn't keep track of what you initialized 'g' with apparently.   But
I suspect that'd be hard to do.

Not in my trivial example. It really seems easy to catch this case, so
I suspect another reason.

Thanks
 
B

Ben Bacarisse

Francis Moreau said:
Maybe because this is how inheritance is usually implemented in C:
casting a generic object into a more specialized object, so generating
a warning in such case would result in ton of confusing warnings in a
lot of places.

If inheritance is done this way (so as to need a warning) then I'd
want the warning! It suggests a possible problem with the
implementation. My reading of type punning (that breaks strict
aliasing) is that it is always a lie, but one you hope you can get
away with. When you convert pointers in a object system you are not
(usually) lying.
Not sure since in the following expression taken from my previsous
example:

struct bar *g = (struct bar *)&global;

it seems trivial to detect type punning since 'struct bar' type is not
compatible with 'struct foo' one.

But nothing has happened yet that needs any warning. On what basis
would the compiler warn about that[1]? The alising rules are broken
when the pointer is used, and that requires tracking some more type
information (not the effective type -- I was wrong about that -- you
know that at the point of use) than gcc apparently does.

[1] Yes, a compiler can warn about anything it likes.
 
T

Tom St Denis

Not in my trivial example. It really seems easy to catch this case, so
I suspect another reason.

What you don't realize is that it's possible for a compiler to
reasonably lose track of what a pointer was initialized with.
Specially if the parse was not designed with that in mind from the get-
go.

Again, I think what you'd really want is a warning for non-void to non-
void casts.

Tom
 
J

jameskuyper

Francis said:
ok so that means that type punning happens only with pointer cast.

No. It indicates that using a pointer cast is part (but not the whole)
of one way of performing type punning. Other ways include the use of
unions and reliance on implicit conversions through void*, as I
indicated in the part of my message that you skipped,
Really ?

I saw ton of code out there which uses pointer casts was used to
implement inheritance in C.

I'm sure it depends upon the body of code you're inspecting. Type
punning is almost always unportable, and (with certain exceptions)
we're not allowed to write code which depends upon unportable
assumptions. Most of the casts that occur in our code come from a
customer mandate that our code must not rely upon implicit
conversions, but I won't count those, because I think that's a
seriously wrong-headed mandate. The remaining casts serve mostly to
make conversions happen that need to occur and which wouldn't occur
implicitly, most commonly as a result of using a variadic function
like members of the printf() or scanf() family.

If I needed to do a lot of inheritance, I'd favor using a language
where it's a built-in feature, rather than wasting time emulating in a
language where it isn't.
However, if you decide that you must emulate inheritance in C, it can
be done without the undefined behavior that is typical of most type-
punning: just define a union type whose members are of the different
struct types, and use the union type to perform the type punning,
where the "common initial sequence" that is referred to in 6.5.2.3p5
is the part that is inherited.
 
J

jameskuyper

jameskuyper wrote:
,,,
... Type
punning is almost always unportable, and (with certain exceptions)
we're not allowed to write code which depends upon unportable
assumptions.

Somehow, I managed to drop the phrase "on my project," from that
sentence during editing.
 
F

Francis Moreau

No. It indicates that using a pointer cast is part (but not the whole)
of one way of performing type punning. Other ways include the use of
unions and reliance on implicit conversions through void*, as I
indicated in the part of my message that you skipped,

Sorry but I was meaning that type punning happens only with pointer
cast when done with a cast operator (ie it doesn't happen with float
cast for example).
I'm sure it depends upon the body of code you're inspecting. Type
punning is almost always unportable, and (with certain exceptions)
we're not allowed to write code which depends  upon unportable
assumptions.

Well, I've seen this sort of code:

struct object {
int type;
int a;
};

struct object_A {
struct object;
int A;
};

void do_something(struct object *obj)
{
if (is_A_object(obj) {
struct object_A *obj_A = (struct object_A *)obj;
obj_A->A++;
}
obj->a++;
}

Where is_A_object() is a function which returns 1 if its parameter is
a struct object_A type object. It uses 'type' field to achieve this.

This should be portable, shouldn't it ?

BTW, I don't claim this is the right approach.

[...]
If I needed to do a lot of inheritance, I'd favor using a language
where it's a built-in feature, rather than wasting time emulating in a
language where it isn't.

I was more thinking in trivial inheritance which can be easily done by
C as the example given above.

Thanks
 
N

Nobody

If I needed to do a lot of inheritance, I'd favor using a language
where it's a built-in feature, rather than wasting time emulating in a
language where it isn't.
However, if you decide that you must emulate inheritance in C, it can
be done without the undefined behavior that is typical of most type-
punning: just define a union type whose members are of the different
struct types, and use the union type to perform the type punning,
where the "common initial sequence" that is referred to in 6.5.2.3p5
is the part that is inherited.

Are you suggesting assigning the structure to a union member to perform
the conversion? Or are you assuming a closed-sum type, i.e. that the
complete set of subclasses is known when the base class is compiled?
The former is inefficient, while the latter defeats the point of having
inheritance.

Or am I missing some trick?
 
J

James Kuyper

Francis said:
Well, I've seen this sort of code:

struct object {
int type;
int a;
};

struct object_A {
struct object;
int A;
};

void do_something(struct object *obj)
{
if (is_A_object(obj) {
struct object_A *obj_A = (struct object_A *)obj;
obj_A->A++;
}
obj->a++;
}

Where is_A_object() is a function which returns 1 if its parameter is
a struct object_A type object. It uses 'type' field to achieve this.

This should be portable, shouldn't it ?

Conversion of a pointer to an struct into a pointer to the type of the
first member of that struct is guaranteed to result in a pointer to
that first member (and vice versa). This is an example of type punning
that does have defined behavior. Most possible uses don't, including
many that are very popular, and do in fact work as expected on the
platforms where they're used.
 
J

James Kuyper

Nobody said:
Are you suggesting assigning the structure to a union member to perform
the conversion? Or are you assuming a closed-sum type, i.e. that the
complete set of subclasses is known when the base class is compiled?

A union type for each sub-class, containing the base class and that one
sub-class, is sufficient to make this work. None of the other
sub-classes need to be known.
The former is inefficient, while the latter defeats the point of having
inheritance.

The difficulties of emulating inheritance in C are precisely the reason
why, if I needed it, I would use a language that provides it as a
built-in feature, if possible. I'll concede that it isn't always
possible, and I have in fact had to do this, but it's clumsy, and error
prone, and not something I would do if I had the freedom to choose a
more suitable language.
 

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,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top