버퍼를 구조물로 해석하는 정확하고 휴대하기 쉬운 방법
내 문제의 맥락은 네트워크 프로그래밍에 있습니다.두 프로그램 사이에 네트워크를 통해 메시지를 보내고 싶다고 합니다.단순화를 위해 메시지가 이렇게 보이고 바이트 순서가 걱정되지 않는다고 가정해 보겠습니다.나는 이 메시지들을 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
바이트/char 포인터가 모든 유형에 별칭을 지정할 수 있으므로 에일리어싱 규칙을 위반하지 않습니다.그 사실이 그은이에기에기r이s,나그ee은toed,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)
/* ... */
}
그러나 이것은 조합이 언제든지 조합원 중 한 명만을 포함한다는 생각에 위배되는 것으로 보입니다.또한 소스 버퍼가 워드/하프 워드 경계에 정렬되지 않으면 정렬 문제가 발생할 수 있습니다.
복사 중:
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)
/* ... */
}
아직 복사해야 하지만 구조의 표현과는 분리되었습니다.하지만 이제 우리는 각 구성원들의 입장과 규모를 명시적으로 말할 필요가 있고, 엔디안성은 훨씬 더 명백한 문제입니다.
관련 정보:
표준을 위반하지 않고 포인터-투-스트루크가 있는 앨리어싱
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의 packed이고,p->payload
는 입니다.void *
. 제가 이해하고 이 대답을 해보면, 이것은 확실히 엄격한 앨리어싱을 깨는 것이 아니고, 따라서 명확하지 않은 행동을 합니다.
이것이 제가 피하려고 했던 것 같은데, 결국 제가 직접 가서 C99 규격을 살펴봤습니다.제가 발견한 내용은 다음과 같습니다. (강조 추가)
공극◦6.3.2.2백
1 무효표식(공백형을 갖는 표현)의 (존재하지 않는) 값은 어떠한 방식으로도 사용할 수 없으며, 암묵적 또는 명시적 전환(공백형을 제외한)은 그러한 표현에 적용할 수 없습니다.다른 형식의 식을 무효 식으로 평가하면 해당 값 또는 지정자가 삭제됩니다.(공백식은 부작용을 평가합니다.)
◦6.3.2.3 포인터
1 보이드의 포인터는 포인터에서 불완전한 또는 객체 유형으로 변환되거나 변환될 수 있습니다.미완성 또는 객체 유형의 포인터는 포인터로 변환되어 무효가 되었다가 다시 되돌아 올 수 있습니다. 그 결과는 원래 포인터와 동일하게 비교됩니다.
그리고 3.14파운드
object1
으로, 그은 값을 수 . 내용이 값을 나타낼 수 있습니다.
§6.5
개체는 다음 유형 중 하나를 포함하는 l 값 표현으로만 저장된 값에 액세스해야 합니다.
— 객체의 유효한 유형과 호환되는 유형,
과 호환되는 -과는의전된의fi,전된의a한f의과는ta,eeilt,
에 해당하는 인 유형 -에는명는형인호효의,형인a호효는의에t,
또는 -체의된는형인호는명에fi,형a호한는,
중 또는 -형중형합합는는한를e형
재귀적으로 포함) 또는위합는된의는원함로원는함)s로,원위(합는g
— 성격 타입
§6.5
된 에 의 한 은 된 입니다 를 입니다 된 은 에 한 된
물체(있는 경우)문자 유형이 아닌 유형의 l 값을 통해 선언된 유형이 없는 개체에 값이 저장된 경우, l 값의 유형은 해당 액세스 및 저장된 값을 수정하지 않는 후속 액세스에 대한 개체의 유효 유형이 됩니다.memcpy 또는 memmove를 사용하여 선언된 유형이 없는 개체로 값을 복사하거나 문자 유형의 배열로 복사하는 경우 해당 액세스 및 값을 수정하지 않는 이후 액세스에 대해 수정된 개체의 유효 유형은 값이 복사되는 개체의 유효 유형입니다.선언된 유형이 없는 개체에 대한 다른 모든 액세스의 경우 개체의 유효 유형은 액세스에 사용되는 l 값의 유형입니다.
◦J.2 미정의 행위
— 무효 표현의 값을 사용하려고 시도하거나, 무효 표현에 암묵적 또는 명시적 변환(무효 제외)을 적용합니다(6.3.2.2).
결론
Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ Δ ΔΔ Δ Δ Δ Δ Δ Δ Δvoid*
을 해도 가 가 유형의 값을 하면 안 됩니다.void
99년도에그러므로 "현실 세계의 예"는 정의되지 않은 행동이 아닙니다.따라서 정렬, 패딩 및 바이트 순서만 처리하면 다음과 같은 수정을 통해 명시적 주조법을 사용할 수 있습니다.
void receive_message(void *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)
/* ... */
}
정확한 추측하신 , 입니다에서 입니다.char
당신의 구조에 버퍼를 넣습니다.다른 대안은 엄격한 별칭 규칙 또는 조합원 1인 활동 규칙을 위반합니다.
한 번 더 말씀드리고 싶은 것은 단일 호스트 및 바이트 순서로 이 작업을 수행하더라도 동일한 옵션으로 구축된 연결 영역의 양 끝을 확인해야 하고 구조물이 동일한 방식으로 패딩되는지 확인해야 한다는 것입니다.실제 직렬화 구현을 고려할 때 최소한 약간의 시간을 할애하여 보다 광범위한 조건을 지원해야 할 경우 큰 업데이트가 발생하지 않도록 하는 것이 좋습니다.
언급URL : https://stackoverflow.com/questions/19165134/correct-portable-way-to-interpret-buffer-as-a-struct
'programing' 카테고리의 다른 글
K&R C 2장의 getbits() 방법 이해에 도움이 필요합니다. (0) | 2023.09.20 |
---|---|
HTML IMG 태그를 이용한 종횡비 유지 방법 (0) | 2023.09.20 |
gdb에서 fork() 후 하위 프로세스는 어떻게 디버깅합니까? (0) | 2023.09.15 |
기존 자바스크립트 프레임워크를 무시하고 활용하는 최첨단 HTML5를 알고 계십니까? (0) | 2023.09.15 |
복제가 선택한 값을 복제하고 있지 않습니다. (0) | 2023.09.15 |