Copying structures of different type

X

Xavier Noria

Given two structures of the same size but different type, does C99
guarantee that pointers to them can be casted one to each other, and
that the order of the elements will be kind of respected?

This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

Do you just access the address and the field offset is always computed
from left to right so that works even with mixed field types?

-- fxn
 
P

Peter Ammon

Xavier said:
Given two structures of the same size but different type, does C99
guarantee that pointers to them can be casted one to each other,

Yes; in fact, it's guaranteed that all pointers to structs can be cast
to one another, same size or not (that is, same alignment restrictions,
same representation).
and
that the order of the elements will be kind of respected?

This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

Yes, you do have those two guarantees AFAIK. But don't extrapolate from
this. f.c[1] is not necessarily the same as bp->c1, because there may
be padding between c0 and c1 in struct bar.
Do you just access the address and the field offset is always computed
from left to right so that works even with mixed field types?

Struct members are stored in the order that you specify, but there may
be padding bytes between any two elements in the struct, or after the
last element in the struct (but not at the beginning). Use the offsetof
macro to find the offset of a field in a struct.
 
P

Peter Pichler

Xavier Noria said:
Given two structures of the same size but different type, does C99
guarantee that pointers to them can be casted one to each other, and
that the order of the elements will be kind of respected?

No. All that is guaranteed is that a pointer to the first field of the
structure can be converted to a pointer to the structure and back.
This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

No. For all the Standard cares, there may be a full script of Romeo and
Juliet between c0 and c1. In most cases, you will get a match there,
but this is not guaranteed.
Do you just access the address and the field offset is always computed
from left to right so that works even with mixed field types?

I am not sure what you are asking here. The . or -> operator involves
computing the offset, yes. This is done at compile time. And yes, they
associate from left to right. Is that what you were asking?

Peter
 
E

Eric Sosman

Peter said:
No. All that is guaranteed is that a pointer to the first field of the
structure can be converted to a pointer to the structure and back.

The guarantees cover more than just this much. We know,
for instance, that all struct pointers have the same size,
the same representation, and the same alignment requirement
(6.2.5/25). There's also a special guarantee (6.5.2.3/5) for
distinct struct types with with a "common initial sequence" of
elements, when those struct types appear in a union.
This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

No. For all the Standard cares, there may be a full script of Romeo and
Juliet between c0 and c1. In most cases, you will get a match there,
but this is not guaranteed.

There's no guarantee that offsetof(struct bar, c1) is one.
But there *is* a guarantee that offsetof(struct bar, c0) and
offsetof(struct foo, c) are both zero.

The only thing I can think of that could torpedo the O.P.'s
code is the possibility that `struct foo' and `struct bar' might
have different alignment requirements (note that the 6.2.5/25
guarantee pertains to the struct pointers, not to the pointed-at
structs). For example, a sufficiently perverse implementation
could allow a `struct foo' to begin pretty much anywhere while
requiring each `struct bar' to start on a 32-byte boundary. In
such a case, even though `bp = (struct bar*) &f' is lossless
(in the sense that a subsequent `(struct foo*) bp' would retrieve
the original value), the value of `bp' could be invalid anyhow,
in the sense that it could not actually point to a `struct bar'
object. Given an invalid `bp', the expression `bp->c0' could
fail even though the `c0' element has no alignment requirement
of its own. Or so it seems to me, at any rate.

Summary: The Standard guarantees that certain kinds of type-
punning with struct pointers will work. Practical implementations
actually permit considerably more than the Standard guarantees.
Still, it's not a practice to be used indiscriminately; as in
other things, overindulgence can lead to hangovers.
 
X

Xavier Noria

Peter Ammon said:
This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

Yes, you do have those two guarantees AFAIK. But don't extrapolate from
this. f.c[1] is not necessarily the same as bp->c1, because there may
be padding between c0 and c1 in struct bar.

Excellent. Thank you very much for the explanation because I was most
likely going to extrapolate :).

This doubt comes from reading in books about networking in Unix that
pointers to struct sockaddr and struct sockaddr_in can be casted to
one another. Now I wonder the actual assumptions for this to be true,
I will ask this in some Unix-related newsgroup.
Struct members are stored in the order that you specify, but there may
be padding bytes between any two elements in the struct, or after the
last element in the struct (but not at the beginning). Use the offsetof
macro to find the offset of a field in a struct.

Just curious, why is that padding permitted? How can those dummy
intermediate bytes be leveraged?

-- fxn
 
J

Jack Klein

Peter Ammon said:
This code illustrates what I mean:

int main(void) {
struct foo { char c[2]; };
struct bar { char c0; char c1; };

struct foo f;
struct bar b, *bp;

f.c[0] = 'a';
bp = (struct bar*) &f; // guaranteed to be valid?
bp->c0 == 'a'; // guaranteed to hold?
}

Yes, you do have those two guarantees AFAIK. But don't extrapolate from
this. f.c[1] is not necessarily the same as bp->c1, because there may
be padding between c0 and c1 in struct bar.

Excellent. Thank you very much for the explanation because I was most
likely going to extrapolate :).

This doubt comes from reading in books about networking in Unix that
pointers to struct sockaddr and struct sockaddr_in can be casted to
one another. Now I wonder the actual assumptions for this to be true,
I will ask this in some Unix-related newsgroup.
Struct members are stored in the order that you specify, but there may
be padding bytes between any two elements in the struct, or after the
last element in the struct (but not at the beginning). Use the offsetof
macro to find the offset of a field in a struct.

Just curious, why is that padding permitted? How can those dummy
intermediate bytes be leveraged?

-- fxn

Some processors are either only able to access data objects of certain
sizes when they are located with certain address alignments, or can
access misaligned data but are less efficient, that it takes more time
or more bus cycles.

An ARM processor can only access 32-bit objects (e.g., int or long) at
addresses evenly divisible by 4. Attempting to access such a value at
an incorrectly aligned address will generate a hardware trap.

A Pentium processor can access 32-bit objects at any address, but most
efficiently at addresses evenly divisible by 4. If such a value is
accessed at any other alignment, it takes longer to read or write it.
If the misaligned address causes the bytes of the object to cross
between two cache lines, the access can take vastly longer.

So if you have a struct:

struct my_struct { char x; long y };

....most compilers for 32-bit processors start the struct on an address
evenly divisible by 4 (or perhaps 8), and leave three padding bytes
after x so that y is also located at an address evenly divisible by 4.
 
X

Xavier Noria

Eric Sosman said:
structs). For example, a sufficiently perverse implementation
could allow a `struct foo' to begin pretty much anywhere while
requiring each `struct bar' to start on a 32-byte boundary. In
such a case, even though `bp = (struct bar*) &f' is lossless
(in the sense that a subsequent `(struct foo*) bp' would retrieve
the original value), the value of `bp' could be invalid anyhow,
in the sense that it could not actually point to a `struct bar'
object. Given an invalid `bp', the expression `bp->c0' could
fail even though the `c0' element has no alignment requirement
of its own. Or so it seems to me, at any rate.

Wow, now I don't see why the cast is permitted at all.

If I understand your answers correctly, for a pointer to `struct foo'
looks like either people store a true `struct foo*' or either there's
no portable way to get to its fields, even the first one (someone said
it is OK for the first one, but I don't know how to reconcile that
with the fact that bp could be actually invalid)!

What's the point then? How can you use a casted pointer portabily?
 
R

Richard Bos

Wow, now I don't see why the cast is permitted at all.

The cast is permitted because explicit conversions between different
struct types is useful in other circumstances (e.g., where the structs
have a common initial member), and while I can't read the Committee's
minds, they probably considered that allowing the useful kinds but
disallowing this one would make the definition of casts and/or structs
needlessly complicated.

Richard
 
C

CBFalconer

Xavier said:
Wow, now I don't see why the cast is permitted at all.

Strictly speaking, I don't believe it is. However the definitions
allow such an illegal cast to "work" in many circumstances, for
some value of "work".

Pointers can be cast to and from void*, with the proviso that the
from must be the same type as that originally cast to void*, apart
from special things supplied by malloc and friends.

Don't ask me for C & V. Maybe Dan can confirm or deny.
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top