Является ли reinterpret_cast плохим при работе с низкоуровневой манипуляцией байтами?

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

Маска имеет тип unsigned char[4], а данные также являются буфером без знака char*.

Я не хочу XOR байт за байтом, я бы предпочел XOR 4 байта за раз.

uint32_t * const end = reinterpret_cast<uint32_t *>(data_+length);
for(uint32_t *i = reinterpret_cast<uint32_t *>(data_); i != end; ++i) {
    *i ^= mask_;
}

Есть ли что-то не так с использованием reinterpret_cast в этой ситуации?

Альтернативой может быть следующий код, который не так понятен и не так быстр:

uint64_t j = 0;
uint8_t *end = data_+length;
for(uint8_t *i = data_; i != end; ++i,++j) {
    *i ^= mask_[j % 4];
}

Я все уши за альтернативы, в том числе те, которые зависят от функций C++11.

2 ответа

Решение

Вот несколько потенциальных проблем с опубликованным подходом:

  1. В некоторых системах объекты типа больше чем char должен быть правильно выровнен, чтобы быть доступным. Типичное требование для uint32_t заключается в том, что объект выровнен по адресу, кратному четырем.
  2. Если length / sizeof(uint32_t) != 0 цикл может никогда не закончиться.
  3. В зависимости от порядка работы системы mask должен содержать разные значения. Если mask производится *reinterpret_cast<uint32_t>(char_mask) подходящего массива это не должно быть массивом.

Если об этих проблемах позаботятся, reinterpret_cast<...>(...) может быть использован в вашей ситуации. Переосмысление значения указателей является одной из причин, по которой эта операция выполняется, а иногда и необходима. Я хотел бы создать подходящий тестовый пример, чтобы убедиться, что он работает должным образом, чтобы избежать необходимости выявлять проблемы при переносе кода на другую платформу.

Лично я бы использовал другой подход, пока профилирование не покажет, что это слишком медленно:

char* it(data);
if (4 < length) {
    for (char* end(data + length - 4); it < end; it += 4) {
        it[0] ^= mask_[0];
        it[1] ^= mask_[1];
        it[2] ^= mask_[2];
        it[3] ^= mask_[3];
    }
}
it != data + length && *it++ ^= mask_[0];
it != data + length && *it++ ^= mask_[1];
it != data + length && *it++ ^= mask_[2];
it != data + length && *it++ ^= mask_[3];

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

Там нет ничего особенно плохого в reinterpret_cast в этом случае. Но будь осторожен.

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

  • заменить != с < в проверке цикла (есть причина, почему люди используют <и это не потому, что они тупые...) и делают завершающие 1-3 байта побайтно
  • расположите буфер так, чтобы размер буфера для части полезной нагрузки был кратен 32 битам, и просто XOR дополнительных байтов. (Предположительно код проверяет длину полезной нагрузки при возврате байтов вызывающей стороне, так что это не имеет значения.)

Кроме того, в зависимости от того, как структурирован код, вам также может понадобиться справиться с неправильным доступом к данным для некоторых процессоров. Если у вас есть весь буферизованный кадр, заголовок и все, в буфере, который выровнен по 32-битам, и если длина полезной нагрузки составляет <126 байтов или>65 535 байтов, то и маскирующий ключ, и полезная нагрузка будут выровнены неправильно.

Как бы то ни было, мой сервер использует что-то вроде первого цикла:

for(int i=0;i<n;++i)
    payload[i]^=key[i&3];

В отличие от 32-битной опции, это в принципе невозможно ошибиться.

Другие вопросы по тегам