смещение битов без знака char и без знака long long пошло не так

#c #visual-c #visual-studio-2015 #x86-64

# #c #visual-c #visual-studio-2015 #x86-64

Вопрос:

Это функция, которую я (хочу) использовать для декодирования чисел из буферов без знака char[] для сетевого взаимодействия.

 inline unsigned long long getULongLongLongInt(const unsigned char* buffer)
{
    unsigned long long number= (buffer[0] << 56);
    number|= (buffer[1] << 48);
    number|= (buffer[2] << 40);
    number|= (buffer[3] << 32);
    number|= (buffer[4] << 24);
    number|= (buffer[5] << 16);
    number|= (buffer[6] << 8);
    number|= buffer[7];
    return number;
}
 

Я получаю предупреждение C4293 ‘<<‘: количество сдвигов отрицательное или слишком большое, неопределенное поведение» четыре раза для самых верхних битовых сдвигов;
Это предупреждение, которое я могу спокойно игнорировать, потому что компилятор не распознает, что я использую неподписанный 64-разрядный int? Я полагаю, что это не так. Но тогда как мне это исправить?

Ответ №1:

Нет, ты не можешь игнорировать это. Операнд buffer[i] имеет тип unsigned char , который, вероятно, повышен до int (а если нет int , то unsigned int ). Если 56 больше или равно разрядности int , то сдвиг равен UB.

Вам нужно записать static_cast<unsigned long long>(buffer[0]) << 56 и так далее, чтобы операнд был не менее 64 бит задолго до сдвига.

Комментарии:

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

2. @blipman17 Это вопрос мнения, но многие люди рекомендуют избегать приведений в стиле C на C , в то время как мало кто найдет какие-либо недостатки в использовании приведения в стиле C , даже если они предпочитают приведение в стиле C.

3. не могу заставить это работать с вашими предложенными правками. Тот же результат.

Ответ №2:

При использовании в выражении unsigned char значения повышаются до int s . Очевидно, что попытка сдвинуть an int на 56 бит будет не очень продуктивной.

Вы должны быть более явными:

  unsigned long long number= ((unsigned long long)buffer[0] << 56);
    number|= ((unsigned long long)buffer[1] << 48);
 

….. и так далее. Вы должны принудительно unsigned long long выполнить преобразование до того, как произойдет операция сдвига.

Комментарии:

1. Продвижение чисел при сдвиге битов. Что вы думаете об этом поведении? Когда это было бы полезно?

2. Это было бы полезно, когда вам нужно получить правильный результат.

3. Да, но почему бы автоматически не привести его к типу, с которым он будет взаимодействовать? в этом случае беззнаковый long long? «просто» преобразовать его в (неподписанный) int звучит для меня как ошибка.

4. Потому что он не «взаимодействует» с an unsigned long long . Другим значением, используемым << оператором, является an int .

Ответ №3:

Я думаю, что лучше всего явно указать намерение и позволить оптимизатору компилятора творить свою магию:

 #include <cstdint>
#include <utility>

template<class Unsigned,
std::enable_if_t<std::is_unsigned<Unsigned>::value>* = nullptr
  >
inline Unsigned decode_int_msb(const unsigned char* buffer)
{
  //
  // some helpful types and constants
  //

  using type = Unsigned;
  static constexpr auto bytes = sizeof(type);
  static constexpr auto bits = bytes * 8;

  //
  // a helpful local function
  //

  auto byte = [buffer](auto i) {
    return type(buffer[i]) << bits - ((i 1) * 8);
  };

  //
  // simplified algorithm here
  //

  type acc = 0;
  for(std::size_t i = 0 ; i < bytes ;   i)
    acc  = byte(i);
    return acc;
}

int main(int argc, char** argv)
{

  // force expansion of some templates

  auto x =  decode_int_msb<unsigned long long>(reinterpret_cast<const unsigned char*>(argv[0]));
  auto y =  decode_int_msb<unsigned long>(reinterpret_cast<const unsigned char*>(argv[0]));
  auto z =  decode_int_msb<unsigned short>(reinterpret_cast<const unsigned char*>(argv[0]));
  return x   y   z;
}
 

пример вывода ассемблером неподписанной версии long long (если она не встроена):

 unsigned long decode_int_msb<unsigned long, (void*)0>(unsigned char const*):
        movzx   ecx, BYTE PTR [rdi 1]
        movzx   edx, BYTE PTR [rdi 2]
        movzx   eax, BYTE PTR [rdi 3]
        mov     rsi, rcx
        sal     rdx, 40
        sal     rsi, 48
        sal     rax, 32
        lea     rcx, [rsi rdx]
        lea     rdx, [rcx rax]
        movzx   eax, BYTE PTR [rdi]
        sal     rax, 56
        add     rax, rdx
        movzx   edx, BYTE PTR [rdi 4]
        sal     rdx, 24
        add     rdx, rax
        movzx   eax, BYTE PTR [rdi 5]
        sal     rax, 16
        add     rdx, rax
        movzx   eax, BYTE PTR [rdi 6]
        sal     rax, 8
        add     rax, rdx
        movzx   edx, BYTE PTR [rdi 7]
        add     rax, rdx
        ret
 

Комментарии:

1. Это звучит интересно наверняка. Я не уверен, что полностью понимаю вас как ученика c , но я посмотрю на эффективную разницу для себя позже в тот же день.

Ответ №4:

измените код следующим образом:

 //unsigned long long number= (buffer[0] << 56);
unsigned long long number= ((buffer[0] << 31) << 25);