私の問題のコンテキストは、ネットワーク プログラミングにあります。2 つのプログラム間でネットワーク経由でメッセージを送信したいとします。簡単にするために、メッセージが次のように表示され、バイト順は問題ではないとします。これらのメッセージを C 構造体として定義する、正しく、移植可能で、効率的な方法を見つけたいと考えています。これには、明示的なキャスト、共用体によるキャスト、コピー、およびマーシャリングという 4 つのアプローチがあります。
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
、バイト/文字ポインターは任意の型に別名を付ける可能性があるため、別名規則に違反していません。ただし、その逆は当てはまらないため、receive_message
エイリアシング ルールに違反し、未定義の動作になります。
ユニオンによるキャスト:
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)
/* ... */
}
ただし、これは、ユニオンには常にそのメンバーの1つしか含まれないという考えに違反しているようです。さらに、ソース バッファーがワード/ハーフワード境界で整列されていない場合、整列の問題が発生する可能性があるようです。
コピー中:
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)
/* ... */
}
まだコピーする必要がありますが、構造体の表現から分離されました。しかし今では、各メンバーの位置とサイズを明示する必要があり、エンディアンはより明白な問題です。
関連情報:
標準に違反することなく、構造体へのポインタを使用して配列をエイリアシングする
厳密なポインターエイリアシングに対してchar *が安全なのはいつですか?
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
実際の例
この状況が他の場所でどのように処理されるかを確認するために、ネットワーク コードの例を探していました。軽量 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] strict-aliasing を破っているため、未定義の動作をしています。