Подтвердить что ты не робот

Правильный, переносимый способ интерпретации буфера как структуры

Контекст моей проблемы заключается в сетевом программировании. Скажем, я хочу отправлять сообщения по сети между двумя программами. Для простоты предположим, что сообщения выглядят так, а порядок байтов не вызывает беспокойства. Я хочу найти правильный, портативный и эффективный способ определения этих сообщений как структуры 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.

4b9b3361

Ответ 1

Я думаю, это то, чего я пытался избежать, но я, наконец, пошел и сам посмотрел на C99. Вот что я нашел (выделено мной):
§6.3.2.2 void

1 (несуществующее) значение выражения void (выражение, которое имеет тип void) не должно использоваться каким-либо образом, а неявные или явные преобразования (за исключением void) не должны быть применяется к такому выражению. Если выражение любого другого типа оценивается как пустота выражение, его значение или обозначение отбрасываются. (Выражение void оценивается для его побочные эффекты.)

§6.3.2.3 Указатели

1 Указатель на void может быть преобразован в указатель или из указателя на любой неполный или объект тип. Указатель на любой неполный или тип объекта может быть преобразован в указатель на void и обратно; результат сравнивается с исходным указателем.

И §3.14

1 объект
область хранения данных в среде исполнения, содержимое которой может представлять Значения

§6.5

Объект должен иметь сохраненное значение, доступ к которому имеет только выражение lvalue, которое имеет один из следующие типы:
- тип, совместимый с эффективным типом объекта,
- квалифицированная версия типа, совместимого с эффективным типом объекта,
- тип, который является подписанным или неподписанным типом, соответствующим эффективному типу объекта,
- тип, который является подписанным или неподписанным типом, соответствующим качественной версии эффективного типа объекта,
- совокупность или тип объединения, который включает один из вышеупомянутых типов среди его членов (в том числе, рекурсивно, члена субагрегата или объединенного союза), или
- тип символа.

§6.5

Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объект, если он есть. Если значение хранится в объекте без объявленного типа через lvalue, имеющий тип, который не является типом символа, тогда тип lvalue становится эффективный тип объекта для этого доступа и для последующих доступов, которые не изменяют сохраненное значение. Если значение копируется в объект, не имеющий объявленного типа, используя memcpy или memmove, или копируется как массив типа символа, тогда эффективный тип модифицированного объекта для этого доступа и для последующих доступов, которые не изменяют value - это эффективный тип объекта, из которого копируется значение, если оно есть. Для все другие обращения к объекту, не имеющему объявленного типа, эффективный тип объекта просто тип lvalue, используемый для доступа.

§J.2 Undefined Поведение

- делается попытка использовать значение выражения void или неявное или явное преобразование (кроме void) применяется к недействительному выражению (6.3.2.2).

Заключение

Это нормально (четко определено), чтобы использовать to-and-from от void*, но не нормально использовать значение типа void в C99. Поэтому "пример реального мира" - это не поведение Undefined. Поэтому явный метод литья может быть использован со следующей модификацией, если по умолчанию выполняются выравнивание, отступы и порядок байтов:

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)
        /* ... */
}

Ответ 2

Единственный правильный способ, как вы предположили, - скопировать данные из буфера char в вашу структуру. Ваши другие альтернативы нарушают строгие правила псевдонимов или правило одного члена профсоюза.

Я хочу сделать еще один момент, чтобы напомнить вам, что даже если вы сделаете это на одном хосте и порядке байтов, не имеет значения, что вам все равно нужно убедиться, что оба конца аранжировки соединений построены с теми же параметрами и что структура дополняется одинаково, типы имеют одинаковый размер и т.д. Я предлагаю взять хотя бы небольшое количество времени, рассматривая реальную реализацию сериализации, чтобы, если вам когда-либо понадобится поддержка более широкого спектра условий, У вас есть большое обновление перед вами.