Работаю программистом. Мои программы успешно работают. Но иногда на меня находит, и я пытаюсь что-нибудь в них переделать "по уму".
Сегодня рассмотрим очень простую задачу. У нас есть массив байтов (допустим, пакет, который мы хотим отправить через сеть) и нам надо в определённое место этого массива положить 32-битное число. Используем порядок байтов little endian (т.е. "обычный").
Напрашиваются два варианта решения:
Первый вариант:
inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
*reinterpret_cast<uint32_t *> (dst) = n;
}
Второй вариант:
inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
dst[0] = static_cast<unsigned char> (n & 255);
dst[1] = static_cast<unsigned char> ((n >> 8) & 255);
dst[2] = static_cast<unsigned char> ((n >> 16) & 255);
dst[3] = static_cast<unsigned char> (n >> 24);
}
Второй вариант более правильный в смысле портируемости и соблюдения стандартов. Проблема в том, что он неэффективный. Говорят, что компилятор умный, что он заметит, что все четыре строчки можно выполнить одной машинной командой, и соптимизирует. Однако, эксперименты показывают, что, например, GCC даже с оптимизацией -O3 по факту этого не делает. Замеры времени показывают, что второй вариант исполняется где-то в полтора раза медленнее, чем первый.
Лично мне неприятно так наплевательски относиться к быстродействию функции, которая у меня будет выполняться тысячу раз в секунду.
Первый вариант работает быстрее, но он работает только если у нас машина с little endian byte order. Но это ладно. Можно сделать так:
inline void put_uint32_le(unsigned char *dst, uint32_t n)
{
#ifdef WE_HAVE_LITTLE_ENDIAN_PROCESSOR
*reinterpret_cast<uint32_t *> (dst) = n;
#else
dst[0] = static_cast<unsigned char> (n & 255);
dst[1] = static_cast<unsigned char> ((n >> 8) & 255);
dst[2] = static_cast<unsigned char> ((n >> 16) & 255);
dst[3] = static_cast<unsigned char> (n >> 24);
#endif
}
Хуже другое. Указатель 'dst' может быть невыровненным. В стандарте написано, что выравнивание это "a number of bytes between successive addresses at which a given object can be allocated". Во всём стандарте выравнивание рассматривается именно в таком ключе - ни одно место в стандарте не говорит о том, что к многобайтному числу можно обратиться по невыровненному указателю.
Более того, я и в документации на компилятор ("man gcc", "info gcc") не нахожу, где бы было написано, что компилятор это поддерживает хотя-бы на intel-архитектуре, хотя-бы в качестве расширения стандарта! Хоть-бы #ifdef'ы какие-нибудь сделать, но как именно их сделать, не могу понять!
То есть, то, что я делаю в первом варианте, приводит к undefined behavior. Да, я так уже делал в сотнях программ, и на intel-совместимых процессорах всегда работало. Да, так делаю не только я один. Но никто не обещал, что в следующей версии компилятора это не заглючит, поэтому так делать нельзя.
Более того, если прищуриться, становится видно, что и второй вариант не абсолютно портируемый. Во-первых, у нас может не быть типа uint32_t, поэтому вместо него надо использовать uint_fast32_t. Во-вторых, у нас, теоретически, unsigned char может состоять более чем из восьми битов. Если первая проблема легко решается, то со второй ума не приложу что делать.
Кто что посоветует?
Есди в первом варианте вы замените присвоение , обединением то вы избежите неопределености с выравниванием
union
{
uint32_t v;
uint8_t c[4];
}value;
Но если у вас есть четко оговореный протокол передачи , то нужно ставить каждый байт на свое место, а для этого преобразовывать между littel-endian и big-endian
VTT:
Что-то вы напридумывали про длинный ассемблер и укладку в стек...
А если подбавить реализьму?
Попробуйте с нижеследующим кодом. Только обязательно укажите версию GCC не позднее 6.3.
#include <string.h>
#include <stdint.h>
inline uint16_t get_uint16_le(const unsigned char *src)
{
return *reinterpret_cast<const uint16_t *> (src);
}
inline void put_uint32_le(unsigned char *dst, uint_fast32_t n)
{
memcpy(dst, &n, 4);
// *reinterpret_cast<uint32_t *> (dst) = n;
}
extern const uint32_t crc32_table[256];
static uint32_t calculate_crc32(const unsigned char *buf, size_t len)
{
uint32_t crc = 0;
while (len--)
{
crc ^= crc32_table[*(buf++)];
}
return crc;
}
void set_crc32(unsigned char *begin)
{
size_t sz = 3 + get_uint16_le(begin);
put_uint32_le(begin + sz, calculate_crc32(begin, sz));
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Есть две формы: Form1 и Form2Они взаимодействую друг с другом, и при выполнении определенных действий во второй форме запускается цикл, идущий...
Решение задачи о рюкзакеУсловия: Одной из классических NP-полных задач является так называемая «Задача о рюкзаке»
Можно ли в QNetworkRequest задать кодировку? А то я загружаю с помощью него сайт а там вместо русских букв кракозяблы