Является ли 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 ответа
Вот несколько потенциальных проблем с опубликованным подходом:
- В некоторых системах объекты типа больше чем
char
должен быть правильно выровнен, чтобы быть доступным. Типичное требование дляuint32_t
заключается в том, что объект выровнен по адресу, кратному четырем. - Если
length / sizeof(uint32_t) != 0
цикл может никогда не закончиться. - В зависимости от порядка работы системы
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-битной опции, это в принципе невозможно ошибиться.