The context of my problem is in network programming. Say I want to send messages over the network between two programs. For simplicity, let's say messages look like this, and byte-order is not a concern. I want to find a correct, portable, and efficient way to define these messages as C structures. I know of four approaches to this: explicit casting, casting through a union, copying, and marshaling.
struct message {
    uint16_t logical_id;
    uint16_t command;
};
Explicit Casting:
void send_message(struct message *msg) {
    uint8_t *bytes = (uint8_t *) msg;
    /* call to write/send/sendto here */
}
void receive_message(uint8_t *bytes, size_t len) {
    assert(len >= sizeof(struct message);
    struct message *msg = (struct message*) bytes;
    /* And now use the message */
    if (msg->command == SELF_DESTRUCT)
        /* ... */
}
My understanding is that send_message does not violate aliasing rules, because a byte/char pointer may alias any type. However, the converse is not true, and so receive_message violates aliasing rules and thus has undefined behavior.
Casting Through a Union:
union message_u {
    struct message m;
    uint8_t bytes[sizeof(struct message)];
};
void receive_message_union(uint8_t *bytes, size_t len) {
    assert(len >= sizeof(struct message);
    union message_u *msgu = bytes;
    /* And now use the message */
    if (msgu->m.command == SELF_DESTRUCT)
        /* ... */
}
However, this seems to violate the idea that a union only contains one of its members at any given time. Additionally, this seems like it could lead to alignment issues if the source buffer isn't aligned on a word/half-word boundary.
Copying:
void receive_message_copy(uint8_t *bytes, size_t len) {
    assert(len >= sizeof(struct message);
    struct message msg;
    memcpy(&msg, bytes, sizeof msg);
    /* And now use the message */
    if (msg.command == SELF_DESTRUCT)
        /* ... */
}
This seems guaranteed to produce the correct result, but of course I would greatly prefer to not have to copy the data.
Marshaling
void send_message(struct message *msg) {
    uint8_t bytes[4];
    bytes[0] = msg.logical_id >> 8;
    bytes[1] = msg.logical_id & 0xff;
    bytes[2] = msg.command >> 8;
    bytes[3] = msg.command & 0xff;
    /* call to write/send/sendto here */
}
void receive_message_marshal(uint8_t *bytes, size_t len) {
    /* No longer relying on the size of the struct being meaningful */
    assert(len >= 4);    
    struct message msg;
    msg.logical_id = (bytes[0] << 8) | bytes[1];    /* Big-endian */
    msg.command = (bytes[2] << 8) | bytes[3];
    /* And now use the message */
    if (msg.command == SELF_DESTRUCT)
        /* ... */
}
Still have to copy, but now decoupled from the representation of the struct. But now we need be explicit with the position and size of each member, and endian-ness is a much more obvious issue.
Related info:
What is the strict aliasing rule?
Aliasing array with pointer-to-struct without violating the standard
When is char* safe for strict pointer aliasing?
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
Real World Example
I've been looking for examples of networking code to see how this situation is handled elsewhere. The light-weight ip has a few similar cases. In the udp.c file lies the following code:
/**
 * Process an incoming UDP datagram.
 *
 * Given an incoming UDP datagram (as a chain of pbufs) this function
 * finds a corresponding UDP PCB and hands over the pbuf to the pcbs
 * recv function. If no pcb is found or the datagram is incorrect, the
 * pbuf is freed.
 *
 * @param p pbuf to be demultiplexed to a UDP PCB (p->payload pointing to the UDP header)
 * @param inp network interface on which the datagram was received.
 *
 */
void
udp_input(struct pbuf *p, struct netif *inp)
{
  struct udp_hdr *udphdr;
  /* ... */
  udphdr = (struct udp_hdr *)p->payload;
  /* ... */
}
where struct udp_hdr is a packed representation of a udp header and p->payload is of type void *. Going on my understanding and this answer, this is definitely [edit- not] breaking strict-aliasing and thus has undefined behavior.
 
     
    