C ++ пример кодирования ужасов или блестящая идея? - PullRequest
12 голосов
/ 21 октября 2008

У предыдущего работодателя мы писали двоичные сообщения, которые должны были передаваться «по проводам» на другие компьютеры. Каждое сообщение имеет стандартный заголовок что-то вроде:

class Header
{
    int type;
    int payloadLength;
};

Все данные были смежными (заголовок, за которым сразу следуют данные). Мы хотели получить полезную нагрузку, учитывая, что у нас есть указатель на заголовок. Традиционно вы можете сказать что-то вроде:

char* Header::GetPayload()
{
    return ((char*) &payloadLength) + sizeof(payloadLength);
}

или даже:

char* Header::GetPayload()
{
    return ((char*) this) + sizeof(Header);
}

Это казалось многословным, поэтому я придумал:

char* Header::GetPayload()
{
    return (char*) &this[1];
}

Сначала это кажется довольно тревожным, возможно, слишком странным для использования - но очень компактным. Было много споров о том, было ли это гениальным или мерзостью.

Так что же это - преступление против кодирования или хорошее решение? У вас когда-нибудь был подобный компромисс?

-Обновление:

Мы попробовали массив нулевого размера, но в то время компиляторы давали предупреждения. В конце концов мы перешли к унаследованной технике: сообщение происходит от заголовка. На практике это прекрасно работает, но в принципе вы говорите сообщение IsA Header - что кажется немного неловким.

Ответы [ 17 ]

1 голос
/ 22 октября 2008

Я на самом деле делаю что-то подобное, как и почти каждая MMO или онлайн-видеоигра, когда-либо написанная. Хотя у них есть понятие, называемое «Пакет», и каждый пакет имеет свою собственную разметку. Таким образом, вы можете иметь:

struct header
{
    short id;
    short size;
}

struct foo
{
    header hd;
    short hit_points;
}


short get_foo_data(char *packet)
{
    return reinterpret_cast<foo*>(packet)->hit_points;
}

void handle_packet(char *packet)
{
    header *hd = reinterpret_cast<header*>(packet);
    switch(hd->id)
    {
        case FOO_PACKET_ID:
            short val = get_foo_data(packet);
        //snip
    }
}

И они делают это для большинства своих пакетов. Некоторые пакеты, очевидно, имеют динамические размеры, и для этих членов они используют поля с префиксом длины и некоторую логику для анализа этих данных.

1 голос
/ 22 октября 2008

Не забывайте, что VC ++ может накладывать на класс значение sizeof(). Поскольку предполагается, что предоставленный пример будет иметь размер 8 байт, он автоматически выравнивается по DWORD, поэтому все должно быть в порядке Чек #pragma pack.

Хотя, я согласен, приведенные примеры являются некоторой степенью кодирования ужасов. Многие структуры данных Win32 включают указатель-заполнитель в структуру заголовка, когда следуют данные переменной длины. Это, вероятно, самый простой способ ссылаться на эти данные после их загрузки в память. Структура MAPI SRowSet является одним из примеров такого подхода.

1 голос
/ 22 октября 2008

Я думаю, что в наше время, в C ++, бросок на C * в стиле C * лишает вас возможности получить награду за «блестящую дизайнерскую идею» без особого слуха.

Я мог бы пойти на:

#include <stdint.h>
#include <arpa/inet.h>

class Header {
private:
    uint32_t type;
    uint32_t payloadlength;
public:
    uint32_t getType() { return ntohl(type); }
    uint32_t getPayloadLength() { return ntohl(payloadlength); }
};

class Message {
private:
    Header head;
    char payload[1]; /* or maybe std::vector<char>: see below */
public:
    uint32_t getType() { return head.getType(); }
    uint32_t getPayloadLength() { return head.getPayloadLength(); }
    const char *getPayload() { return payload; }
};

Это предполагает, конечно, C99-ish POSIX: для портирования на платформы, отличные от POSIX, вам придется самостоятельно определить один или оба из uint32_t и ntohl, в зависимости от того, что платформа предлагает. Обычно это не сложно.

Теоретически вам могут понадобиться макеты прагмы в обоих классах. На практике я был бы удивлен, учитывая фактические поля в этом случае. Эту проблему можно избежать, читая / записывая данные из / в iostreams по одному полю за раз, вместо того, чтобы пытаться создать байты сообщения в памяти и затем записать его за один раз. Это также означает, что вы можете представлять полезную нагрузку чем-то более полезным, чем char [], что, в свою очередь, означает, что вам не нужно иметь максимальный размер сообщения, или возиться с malloc и / или новым размещением, или чем-то еще. Конечно, это вносит немного накладных расходов.

0 голосов
/ 03 февраля 2009

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

0 голосов
/ 06 января 2009

Я не люблю использовать такие слова, как "преступление". Я бы скорее указал, что & this [1], кажется, делает предположения о расположении памяти, с которым компилятор может не согласиться. Например, любой компилятор может по своим собственным причинам (например, выравнивание) вставлять фиктивные байты в любом месте структуры. Я бы предпочел метод, который дает больше гарантии получения правильного смещения, если компиляторы или параметры были изменены.

0 голосов
/ 22 октября 2008

Я голосую за & это [1]. Я видел, что он довольно часто использовался при разборе файлов, которые были загружены в память (которые могут в равной степени включать в себя полученные пакеты). С первого взгляда это может показаться немного странным, но я думаю, что это означает, что это должно быть сразу очевидно: это адрес памяти, который находится сразу за этим объектом. Это хорошо, потому что трудно ошибиться.

0 голосов
/ 21 октября 2008

Возможно, вам следовало использовать подробный метод, но заменить его макросом #define? Таким образом, вы можете использовать свое сокращенное обозначение при наборе текста, но любой, кому требуется отладка кода, может выполнить это без проблем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...