В C ++, относительно сдвига битов и типов данных приведения - PullRequest
5 голосов
/ 15 октября 2011

Недавно я задал здесь вопрос о переполнении стека о том, как привести мои данные из 16-разрядного целого числа, за которым следует неопределенное количество памяти void * -cast, в std :: vector беззнаковых символов для использованиябиблиотека сокетов, известная как NetLink, которая использует функцию, сигнатура которой выглядит следующим образом, для отправки необработанных данных:

void rawSend(const vector<unsigned char>* data);

(для справки вот такой вопрос: Приведение целого числа без знака + строки к знаку без знакаvector )

На вопрос был успешно дан ответ, и я благодарен тем, кто ответил.Майк ДеСимоне ответил примером функции send_message (), которая преобразует данные в формат, который принимает NetLink (std :: vector), который выглядит следующим образом:

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
    vector<unsigned char> buffer;
    buffer.reserve(sizeof(uint16_t) + rawDataSize);
    buffer.push_back(opcode >> 8);
    buffer.push_back(opcode & 0xFF);
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
    buffer.insert(buffer.end(), base, base + rawDataSize);
    socket->rawSend(&buffer);
}

Это выглядит именно такМне нужно было, и поэтому я решил написать сопровождающую функцию receive_message () ...

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

Возвращаясь к теме написания функции receive_message (), моей отправной точкой, как вы можете себе представить, является функция NetReink rawRead (), сигнатура которой выглядит следующим образом:

vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);

Это похоже на мой кодначнется что-то вроде этого:

void receive_message(NLSocket* socket, uint16_t* opcode, const void** rawData)
{
    std::vector<unsigned char, std::allocator<unsigned char>>* buffer = socket->rawRead();
    std::allocator<unsigned char> allocator = buffer->get_allocator(); // do I even need this allocator?  I saw that one is returned as part of the above object, but...
    // ...
}

После этого первого вызова rawRead (), мне кажется, мне нужно будет перебрать вектор, извлечь из него данные и отменить операции битового сдвига, а затем вернуть данныев * rawData и * код операции.Опять же, я не очень знаком с битшифтингом (я немного погуглил, чтобы понять синтаксис, но я не понимаю , почему приведенный выше код send_message () вообще требует смещения), так что я впотеря для моего следующего шага здесь.

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

Заранее спасибо!

Ответы [ 2 ]

3 голосов
/ 15 октября 2011

сигнатура функции библиотеки * hellip;

    void rawSend( const vector<unsigned char>* data );

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

Функция подписи библиотеки & hellip;

    vector<unsigned char>* rawRead(unsigned bufferSize = DEFAULT_BUFFER_SIZE, string* hostFrom = NULL);

хуже: он не просто требует от вас создания std::string, если вы хотите указать & ldquo; hostFrom & rdquo; (что бы это ни значило на самом деле), но это излишне требует от вас освобождения результата vector. По крайней мере, если есть смысл использовать тип результата. Которого, конечно же, может и не быть.

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


Как существующий код использования & hellip;

void send_message(NLSocket* socket, uint16_t opcode, const void* rawData, size_t rawDataSize)
{
    vector<unsigned char> buffer;
    buffer.reserve(sizeof(uint16_t) + rawDataSize);
    buffer.push_back(opcode >> 8);
    buffer.push_back(opcode & 0xFF);
    const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData));
    buffer.insert(buffer.end(), base, base + rawDataSize);
    socket->rawSend(&buffer);
}

работает:

  • Вызов reserve - это случай преждевременной оптимизации. Он пытается заставить vector сделать только одно выделение буфера (выполненное в этот момент) вместо, возможно, двух или более. Гораздо лучшим лекарством от очевидной неэффективности построения vector является использование более разумной библиотеки.

  • buffer.push_back(opcode >> 8) помещает старшие 8 бит (предполагаемого) 16-битного количества opcode в начало вектора. Размещая верхнюю часть, наиболее значимая часть, во-первых, называется форматом big endian . Ваш код чтения на другом конце должен принимать формат с прямым порядком байтов. И точно так же, если бы этот отправляющий код использовал формат little endian , тогда код чтения должен был бы принимать формат little endian. Таким образом, это всего лишь решение о формате данных, но, учитывая решение, код на обоих концах должен его придерживаться.

  • Вызовы buffer.push_back(opcode & 0xFF) размещают младшие 8 бит opcode после старших бит, как правильно для старшего байта.

  • Декларация const unsigned char* base(reinterpret_cast<const unsigned char*>(rawData)) просто называет правильно введенный указатель на ваши данные, называя его base. Тип const unsigned char* подходит, поскольку он разрешает байтовый уровень адресную арифметику . Исходный формальный тип аргумента const void* не допускает адресную арифметику.

  • buffer.insert(buffer.end(), base, base + rawDataSize) добавляет данные к вектору. Выражение base + rawDataSize является адресной арифметикой, которую включило предыдущее объявление.

  • socket->rawSend(&buffer) - последний вызов метода SillyLibrary & rsquo; s rawSend.


Как обернуть вызов в функцию SillyLibrary rawRead.

Во-первых, определите имя для типа данных байта (всегда хорошая идея, чтобы называть вещи):

typedef unsigned char Byte;
typedef ptrdiff_t Size;

Обратитесь к документации о том, как освободить / уничтожить / удалить (при необходимости) результат функции SillyLibrary:

void deleteSillyLibVector( vector<Byte> const* p )
{
    // perhaps just "delete p", but it depends on the SillyLibrary
}

Теперь для операции отправки, включающей std::vector, была просто боль. Для операции приема все наоборот. Создание динамического массива и его безопасная и эффективная передача в виде результата функции - это как раз то, для чего была разработана std::vector.

Однако операция отправки была всего лишь одним вызовом.

Для операции приема это возможно , в зависимости от конструкции SillyLibrary, вам необходимо выполнить цикл для выполнения количества входящих вызовов до тех пор, пока вы не получите все данные. Вы не предоставляете достаточно информации для этого. Но код ниже показывает нижний уровень чтения , который может вызвать ваш код цикла, накапливая данные в vector:

Size receive_append( NLSocket& socket, vector<Byte>& data )
{
    vector<Byte> const* const result = socket.raw_read();

    if( result == 0 )
    {
        return 0;
    }

    struct ScopeGuard
    {
        vector<Byte>* pDoomed;
        explicit ScopeGuard( vector<Byte>* p ): pDoomed( p ) {}
        ~ScopeGuard() { deleteSillyLibVector( pDoomed ); }
    };

    Size const nBytesRead = result->size();
    ScopeGuard cleanup( result );

    data.insert( data.end(), result->begin(), result->end() );
    return nBytesRead;
}

Обратите внимание на использование деструктора для очистки, что делает это исключение более безопасным.В данном конкретном случае единственным возможным исключением является std::bad_alloc, что в любом случае довольно фатально.Но общая техника использования деструктора для очистки, для безопасности исключений, очень стоит знать и использовать как нечто само собой разумеющееся (хотя обычно не требуется определять какой-либо новый класс, но при работе с классом SillyLibraryвозможно, придется это сделать).

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

Отказ от ответственности: нецензурный код.

Приветствия & hth.,

0 голосов
/ 15 октября 2011

Чтобы перевести бит-трейдинг в термины без бит-фидлинга, opcode >> 8 эквивалентно opcode / 256, а opcode & 0xFF эквивалентно opcode - ((opcode / 256) * 256). Остерегайтесь округления / усечения.

Представьте, что opcode состоит из двух частей: ophi и oplo, каждый со значениями 0..255. opcode == (ophi * 256) + oplo.

Некоторые дополнительные подсказки ...

0xFF  == 255 == binary  11111111 == 2^8 - 1
0x100 == 256 == binary 100000000 == 2^8

              opcode
         /              \
Binary : 1010101010101010
         \      /\      /
           ophi    oplo

Причиной этого является в основном порядок байтов для записи шестнадцати битного значения в побайтный поток данных. Сетевой поток имеет свое собственное правило, в котором «большой конец» значения должен отправляться первым, независимо от того, как это обрабатывается по умолчанию на любой конкретной платформе. Этот send_message, в основном, деконструирует шестнадцатеричное значение для его отправки. Вам нужно будет прочитать два блока, а затем восстановить шестнадцатеричное значение.

Независимо от того, кодируете ли вы реконструкцию как opcode = (ophi * 256) + oplo; или как opcode == (ophi << 8) | oplo;, это в основном дело вкуса - оптимизатор поймет эквивалентность и выяснит, что наиболее эффективно в любом случае.

Кроме того, нет, я не думаю, что вам не нужен распределитель. Я даже не уверен, что использование vector является хорошей идеей, учитывая, что вы используете параметр const void** rawData, но, вероятно, это так, и вы должны сделать reserve перед чтением в него. Затем добавьте соответствующие фрагменты (два байта для восстановления кода операции плюс содержимое массива).

Большая проблема, которую я вижу - откуда вы знаете размер необработанных данных, которые вы будете читать? Он не является параметром receive_message и не предоставляется самим потоком данных.

...