Контекст моей проблемы заключается в сетевом программировании. Скажем, я хочу отправлять сообщения по сети между двумя программами. Для простоты предположим, что сообщения выглядят так, а порядок байтов не вызывает беспокойства. Я хочу найти правильный, портативный и эффективный способ определения этих сообщений как структуры C. Я знаю четыре подхода к этому: явное литье, кастинг через объединение, копирование и маршалинг.
struct message {
uint16_t logical_id;
uint16_t command;
};
Явное литье:
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)
/* ... */
}
Я понимаю, что send_message
не нарушает правила псевдонимов, потому что указатель byte/char может быть псевдонимом любого типа. Однако обратное неверно, поэтому receive_message
нарушает правила псевдонимов и, следовательно, имеет поведение undefined.
Кастинг через союз:
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)
/* ... */
}
Однако это, похоже, нарушает идею о том, что профсоюз содержит только один из его членов в любой момент времени. Кроме того, похоже, что это может привести к проблемам выравнивания, если исходный буфер не выровнен по границе слова/полуслова.
Копирование:
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)
/* ... */
}
Кажется, что это гарантирует правильный результат, но, конечно, я бы предпочел не копировать данные.
Маршалинг
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)
/* ... */
}
По-прежнему нужно копировать, но теперь отделяться от представления структуры. Но теперь нам нужно быть явным с положением и размером каждого члена, а endian-ness - гораздо более очевидная проблема.
Сопутствующая информация:
Что такое строгое правило псевдонимов?
Алиасирующий массив с указателем на структуру без нарушения стандарта
Когда char * безопасен для строгого сглаживания указателей?
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
Пример реального мира
Я искал примеры сетевого кода, чтобы увидеть, как эта ситуация обрабатывается в другом месте. light-weight ip имеет несколько подобных случаев. В файле udp.c находится следующий код:
/**
* 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;
/* ... */
}
где struct udp_hdr
- это упакованное представление заголовка udp, а p->payload
имеет тип void *
. Понимая мое понимание и этот ответ, это определенно [edit- not] нарушает строгое сглаживание и, следовательно, имеет поведение undefined.