programing

버퍼를 구조물로 해석하는 정확하고 휴대하기 쉬운 방법

padding 2023. 9. 15. 20:50
반응형

버퍼를 구조물로 해석하는 정확하고 휴대하기 쉬운 방법

내 문제의 맥락은 네트워크 프로그래밍에 있습니다.두 프로그램 사이에 네트워크를 통해 메시지를 보내고 싶다고 합니다.단순화를 위해 메시지가 이렇게 보이고 바이트 순서가 걱정되지 않는다고 가정해 보겠습니다.나는 이 메시지들을 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)
        /* ... */
}

아직 복사해야 하지만 구조의 표현과는 분리되었습니다.하지만 이제 우리는 각 구성원들의 입장과 규모를 명시적으로 말할 필요가 있고, 엔디안성은 훨씬 더 명백한 문제입니다.

관련 정보:

엄격한 별칭 규칙은 무엇입니까?

표준을 위반하지 않고 포인터-투-스트루크가 있는 앨리어싱

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의 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* 을 해도 가 가 유형의 값을 하면 안 됩니다.void99년도에그러므로 "현실 세계의 예"는 정의되지 않은 행동이 아닙니다.따라서 정렬, 패딩 및 바이트 순서만 처리하면 다음과 같은 수정을 통해 명시적 주조법을 사용할 수 있습니다.

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

반응형